Pull master and resolve conflicts

This commit is contained in:
Joshen Lim committed 2023-07-03 18:27:38 +08:00
commit 23e7c845fa
108 files changed
+3825 -389

No files matched your search

@@ -1183,6 +1183,20 @@ export const integrations: NavMenuConstant = {
url: '/guides/integrations',
items: [
{ name: 'Overview', url: '/guides/integrations/integrations' },
{
name: 'OAuth Apps (Beta)',
url: undefined,
items: [
{
name: 'Publish an OAuth App',
url: '/guides/integrations/oauth-apps/publish-an-oauth-app',
},
{
name: 'Authorize an OAuth App',
url: '/guides/integrations/oauth-apps/authorize-an-oauth-app',
},
],
},
{
name: 'Auth',
url: undefined,
@@ -0,0 +1,54 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'authorize-an-oauth-app',
title: 'Authorize an OAuth App (Beta)',
description: 'Authorize an OAuth App',
}
## Overview
This guide steps through implementing **Sign-in with Supabase**. Once you've added OAuth2.0 support to your main application, you will receive an access and refresh token from your users that choose to **Sign in with Supabase** on your application.
The access token returned will grant your application full access to the [Management API](https://supabase.com/docs/reference/api/introduction) on behalf of the user.
## Implement the OAuth 2.0 flow
Once you've published your OAuth App on Supabase, you can use the OAuth 2.0 protocol to seek consent from Supabase users to access their organization or project.
1. Within your app's UI, redirect the user to [`https://api.supabase.com/v1/oauth/authorize`](<https://api.supabase.com/api/v1#/oauth%20(beta)/authorize>). Make sure to include all required query parameters such as:
- `client_id` A UUID uniquely identifying your OAuth app in Supabase.
- `redirect_uri` The URL where Supabase will redirect the user after providing consent.
- `scope` The only scope supported is `all`. Scoped access is not available at this time.
- `response_type` The value `code`.
- `state` Information about the state of your app. Note that `redirect_uri` and `state` cannot both exceed 4kB in size.
- We strongly recommend using the PKCE flow for increased security. Generate a random value before taking the user to the authorize endpoint. This value is called code verifier. Hash it with SHA256 and include it as the `code_challenge` parameter, while setting `code_challenge_method` to `s256`. In the next step, you would need to provide the code verifier to get the first access and refresh token.
2. Once the user consents to providing API access to your OAuth App, Supabase will redirect the user to the `redirect_uri` endpoint you provided in the previous step. The URL will contain these query parameters:
- `code` An authorization code you should exchange with Supabase to get the access and refresh token.
- `state` The value you provided in the previous step, to help you associate the request with the user. The `state` property returned here should be compared to the `state` you sent previously.
3. Exchange the authorization code for an access and refresh token by calling `POST https://api.supabase.com/v1/oauth/token` and including the following query parameters:
- `grant_type` The value `authorization_code`.
- `code` The `code` returned in the previous step.
- `client_id` The unique client ID identifying your OAuth App.
- `client_secret` The secret that authenticates your OAuth App to Supabase.
- `redirect_uri` This must be exactly the same URL used in the first step.
- If you used the PKCE flow in the first step, include the code verifier as `code_verifier`.
## Refresh an access token
You can use the [`POST /v1/oauth/token`](<https://api.supabase.com/api/v1#/oauth%20(beta)/token>) endpoint to refresh an access token using the refresh token returned at the end of the previous section.
If the user has revoked access to your application, you will not be able to refresh a token. Furthermore, access tokens will stop working. Make sure you handle HTTP Unauthorized errors when calling any Supabase API.
## Access the Management API using the access token
Refer to [this section](/docs/reference/api/introduction#authentication) to learn more about authentication with the Management API.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,25 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'publish-an-oauth-app',
title: 'Publish an OAuth App (Beta)',
description: 'Publish an OAuth App',
}
## Overview
This guide steps through publishing an OAuth app under your organization.
## Publishing an OAuth App
1. In your organization's settings, navigate to the [**OAuth Apps**](/dashboard/org/_/apps) tab.
2. In the upper-right section of the page, click **Add application**.
3. In "Application name", fill in the name of your OAuth app.
4. In "Website URL", fill in the URL of the app that's using the OAuth app.
5. Upload a logo for your OAuth app.
6. In "Authorization callback URLs", add the callback URL of your app.
7. At the bottom of the page, click **Confirm**.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1
View File
@@ -42859,6 +42859,7 @@
"eslint-config-next": "^13.3.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "7.28.0",
"mini-svg-data-uri": "^1.4.3",
"tailwindcss-radix": "^2.0.0"
}
},
+32 -1
View File
@@ -1,15 +1,46 @@
import { useRouter } from 'next/router'
import { useMemo } from 'react'
/**
* Helper to convert kebab case to camel case
*/
function convertToCamelCase(key: string): string {
if (!key.includes('-')) {
return key
}
const parts = key.split('-')
const capitalizedParts = parts.map((part, index) => {
if (index === 0) {
return part
}
return part.charAt(0).toUpperCase() + part.slice(1)
})
return capitalizedParts.join('')
}
export function useParams(): {
[k: string]: string | undefined
} {
const { query } = useRouter()
const modifiedQuery = {
...query,
}
// Convert kebab case keys to camel case
Object.keys(modifiedQuery).forEach((key) => {
const modifiedKey = convertToCamelCase(key)
if (modifiedKey !== key) {
modifiedQuery[modifiedKey] = modifiedQuery[key]
delete modifiedQuery[key]
}
})
return useMemo(
() =>
Object.fromEntries(
Object.entries(query).map(([key, value]) => {
Object.entries(modifiedQuery).map(([key, value]) => {
if (Array.isArray(value)) {
return [key, value[0]]
} else {
+2 -1
View File
@@ -15,6 +15,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "7.28.0",
"tailwindcss-radix": "^2.0.0",
"@tailwindcss/typography": "^0.5.9"
"@tailwindcss/typography": "^0.5.9",
"mini-svg-data-uri": "^1.4.3"
}
}
+23 -12
View File
@@ -3,6 +3,7 @@ const forms = require('@tailwindcss/forms')
const plugin = require('tailwindcss/plugin')
const radixUiColors = require('@radix-ui/colors')
const brandColors = require('./default-colors')
const svgToDataUri = require('mini-svg-data-uri')
const { default: flattenColorPalette } = require('tailwindcss/lib/util/flattenColorPalette')
@@ -371,18 +372,28 @@ const uiConfig = {
highlight: (value) => ({ boxShadow: `inset 0 1px 0 0 ${value}` }),
},
{ values: flattenColorPalette(theme('backgroundColor')), type: 'color' }
),
matchUtilities(
{
subhighlight: (value) => ({
boxShadow: `inset 0 -1px 0 0 ${value}`,
}),
},
{
values: flattenColorPalette(theme('backgroundColor')),
type: 'color',
}
)
)
matchUtilities(
{
subhighlight: (value) => ({
boxShadow: `inset 0 -1px 0 0 ${value}`,
}),
},
{
values: flattenColorPalette(theme('backgroundColor')),
type: 'color',
}
)
matchUtilities(
{
'bg-grid': (value) => ({
backgroundImage: `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`
)}")`,
}),
},
{ values: flattenColorPalette(theme('backgroundColor')), type: 'color' }
)
},
require('tailwindcss-radix')(),
forms,
+1
View File
@@ -63,6 +63,7 @@ export * from './src/components/Form'
export * from './src/components/Command'
// layout
export * from './src/components/LoadingLine'
// banners
export * from './src/layout/banners'
@@ -7,7 +7,7 @@ import { DetailedHTMLProps, HTMLAttributes, KeyboardEventHandler } from 'react'
import { Modal } from '../Modal'
import { ModalProps } from '../Modal/Modal'
import { useCommandMenu } from './CommandMenuProvider'
import { LoadingLine } from './LoadingLine'
import { LoadingLine } from '../LoadingLine/LoadingLine'
type CommandPrimitiveElement = React.ElementRef<typeof CommandPrimitive>
type CommandPrimitiveProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive>
@@ -1,5 +1,5 @@
import React from 'react'
import { cn } from './../../lib/utils'
import { cn } from '../../lib/utils'
export const LoadingLine = ({ loading }: { loading: boolean }) => {
return (
@@ -0,0 +1 @@
export * from './LoadingLine'
+5 -8
View File
@@ -37,8 +37,12 @@ const Tabs: React.FC<TabsProps> & TabsSubComponents = ({
addOnBefore,
addOnAfter,
listClassNames,
children,
children: _children,
}) => {
// toArray is used here to filter out invalid children
// another method would be to use React.Children.map
const children = React.Children.toArray(_children) as PanelPropsProps[]
const [activeTab, setActiveTab] = React.useState(
defaultActiveId ??
// if no defaultActiveId is set use the first panel
@@ -50,13 +54,6 @@ const Tabs: React.FC<TabsProps> & TabsSubComponents = ({
let __styles = styleHandler('tabs')
if (children && !Array.isArray(children)) {
children = [children]
}
// Exclude children that are just conditional elements
children = children.filter((it) => it)
const tabIds = children.map((tab) => tab.props.id)
const { groupActiveId, setGroupActiveId } = useTabGroup(tabIds)
@@ -0,0 +1,267 @@
import dayjs from 'dayjs'
import Image from 'next/image'
import React from 'react'
import { Markdown } from 'components/interfaces/Markdown'
import { Integration, IntegrationProjectConnection } from 'data/integrations/integrations.types'
import { useProjectsQuery } from 'data/projects/projects-query'
import { BASE_PATH } from 'lib/constants'
import { Badge, Button, IconArrowRight, IconGitHub, IconSquare, cn } from 'ui'
const ICON_STROKE_WIDTH = 2
const ICON_SIZE = 14
export interface IntegrationInstallationProps extends React.RefAttributes<HTMLLIElement> {
title: string
integration: Integration
}
type HandleIconType = Integration['integration']['name'] | 'Supabase'
const HandleIcon = ({ type, className }: { type: HandleIconType; className?: string }) => {
switch (type) {
case 'GitHub':
return <IconGitHub strokeWidth={ICON_STROKE_WIDTH} size={ICON_SIZE} />
break
case 'Netlify':
return <IconSquare strokeWidth={ICON_STROKE_WIDTH} size={ICON_SIZE} />
break
case 'Vercel':
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="white"
viewBox="0 0 512 512"
className={cn('w-3.5', className)}
>
<path fill-rule="evenodd" d="M256,48,496,464H16Z" />
</svg>
)
break
case 'Supabase':
return <img src={`${BASE_PATH}/img/supabase-logo.svg`} alt="Supabase" className="w-3.5"></img>
break
default:
return <></>
break
}
}
const Avatar = ({ src }: { src: string | undefined }) => {
return (
<div className="relative border shadow-lg w-8 h-8 rounded-full overflow-hidden">
<Image
src={src || ''}
width={30}
height={30}
layout="fill"
alt="avatar"
className="relative"
/>
</div>
)
}
const IntegrationInstallation = React.forwardRef<HTMLLIElement, IntegrationInstallationProps>(
({ title, integration, ...props }, ref) => {
const IntegrationIconBlock = () => {
return (
<div className="bg-black text-white w-8 h-8 rounded flex items-center justify-center">
<HandleIcon type={integration.integration.name} />
</div>
)
}
return (
<li
ref={ref}
key={integration.id}
className="bg border shadow-sm flex justify-between items-center px-8 py-4 rounded-lg"
{...props}
>
<div className="flex gap-6 items-center">
<div className="flex gap-3 items-center">
<div className="flex -space-x-1">
<IntegrationIconBlock />
<Avatar src={integration?.metadata?.account.avatar} />
</div>
</div>
<div className="flex flex-col gap-0">
<div className="flex items-center gap-2">
<span className="text-scale-1200 font-medium">
{/* {title} integration connection •{' '} */}
{integration.metadata?.account.name || integration.metadata?.gitHubConnectionOwner}
</span>
<Badge color="scale" className="capitalize">
{integration.metadata?.account.type}
</Badge>
</div>
<div className="flex flex-col gap-0">
<span className="text-scale-900 text-sm">
Created {dayjs(integration.inserted_at).fromNow()}
</span>
<span className="text-scale-900 text-sm">
Added by {integration?.added_by?.primary_email}
</span>
</div>
</div>
</div>
<Button type="default">Manage</Button>
</li>
)
}
)
export interface IntegrationConnectionProps extends React.HTMLAttributes<HTMLLIElement> {
connection: IntegrationProjectConnection
type: Integration['integration']['name']
actions?: React.ReactNode
}
const IntegrationConnection = React.forwardRef<HTMLLIElement, IntegrationConnectionProps>(
({ className, connection, type, actions, ...props }, ref) => {
const { data: projects } = useProjectsQuery()
const project = projects?.find((project) => project.ref === connection.supabase_project_ref)
return (
<li
ref={ref}
key={connection.id}
{...props}
className={cn('ml-6 pl-8 pb-2 border-l border-scale-600 dark:border-scale-400', 'relative')}
>
<div className="absolute w-8 rounded-bl-full border-b border-l border-scale-600 dark:border-scale-400 h-10 -left-px"></div>
<div className="bg border shadow-sm flex justify-between items-center px-8 py-4 rounded-lg">
<div className="flex flex-col gap-1">
<div className="flex gap-2 items-center">
<HandleIcon type={'Supabase'} />
<span>{project?.name}</span>
<IconArrowRight size={14} className="text-scale-900" strokeWidth={1.5} />
{!connection?.metadata?.framework ? (
<div className="bg-black text-white w-4 h-4 rounded flex items-center justify-center">
<HandleIcon type={'Vercel'} className={'!w-2.5'} />
</div>
) : (
<img
src={`${BASE_PATH}/img/icons/frameworks/${connection.metadata.framework}.svg`}
width={21}
height={21}
alt={`icon`}
/>
)}
<span>{connection.metadata?.name}</span>
</div>
<div className="flex flex-col gap-0">
<span className="text-scale-900 text-sm">
Connected {dayjs(connection?.inserted_at).fromNow()}
</span>
<span className="text-scale-900 text-sm">
Added by {connection?.added_by?.primary_email}
</span>
</div>
</div>
<div>{actions}</div>
</div>
</li>
)
}
)
const IntegrationConnectionOption = React.forwardRef<HTMLLIElement, IntegrationConnectionProps>(
({ className, connection, type, ...props }, ref) => {
const { data: projects } = useProjectsQuery()
const project = projects?.find((project) => project.ref === connection.supabase_project_ref)
return (
<li
ref={ref}
key={connection.id}
{...props}
className={cn('bg border shadow-sm flex justify-between items-center px-8 py-4 rounded-lg')}
>
<div className="flex flex-col gap-1">
<div className="flex gap-2 items-center">
<HandleIcon type={'Supabase'} />
<span>{project?.name}</span>
<IconArrowRight size={14} className="text-scale-900" strokeWidth={1.5} />
<HandleIcon type={type} />
<span>{connection.metadata.name}</span>
</div>
<span className="text-scale-900 text-sm">
Connected {dayjs(connection.inserted_at).fromNow()}
</span>
</div>
<Button type="default">Connect</Button>
</li>
)
}
)
const EmptyIntegrationConnection = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
{...props}
className={cn('ml-6 pl-8 pb-2 border-l', 'last:border-l-transparent', 'relative', className)}
>
<div className="absolute w-8 rounded-bl-full border-b border-l border-scale-600 dark:border-scale-400 h-10 -left-px"></div>
<div
className={cn(
'w-full',
'border border-dashed bg-scale-300 dark:bg-scale-100 border-scale-600 dark:border-scale-400',
'',
'flex h-20 px-10 rounded-lg justify-center items-center'
)}
>
<Button type="default">Add new project connection</Button>
</div>
</div>
)
})
interface IntegrationConnectionHeader extends React.HTMLAttributes<HTMLDivElement> {
name?: string
markdown?: string
}
const IntegrationConnectionHeader = React.forwardRef<HTMLDivElement, IntegrationConnectionHeader>(
({ className, name, markdown = '', ...props }, ref) => {
return (
<div
{...props}
ref={ref}
className={cn(
'border-l border-scale-600 dark:border-scale-400 ml-6 pl-8 pt-6 pb-3',
className
)}
>
<Markdown content={markdown} className="" />
</div>
)
}
)
IntegrationInstallation.displayName = 'IntegrationInstallation'
IntegrationConnection.displayName = 'IntegrationConnection'
IntegrationConnectionHeader.displayName = 'IntegrationConnectionHeader'
EmptyIntegrationConnection.displayName = 'EmptyIntegrationConnection'
IntegrationConnectionOption.displayName = 'IntegrationConnectionOption'
export {
EmptyIntegrationConnection,
IntegrationConnection,
IntegrationConnectionHeader,
IntegrationConnectionOption,
IntegrationInstallation,
}
@@ -0,0 +1,52 @@
export const ENV_VAR_KEYS = {
POSTGRES_URL: {
key: 'POSTGRES_URL',
type: 'encrypted',
},
POSTGRES_PRISMA_URL: {
key: 'POSTGRES_PRISMA_URL',
type: 'encrypted',
},
POSTGRES_URL_NON_POOLING: {
key: 'POSTGRES_URL_NON_POOLING',
type: 'encrypted',
},
POSTGRES_USER: {
key: 'POSTGRES_USER',
type: 'encrypted',
},
POSTGRES_HOST: {
key: 'POSTGRES_HOST',
type: 'encrypted',
},
POSTGRES_PASSWORD: {
key: 'POSTGRES_PASSWORD',
type: 'encrypted',
},
POSTGRES_DATABASE: {
key: 'POSTGRES_DATABASE',
type: 'encrypted',
},
SUPABASE_ANON_KEY: {
key: 'SUPABASE_ANON_KEY',
type: 'encrypted',
},
SUPABASE_URL: {
key: 'SUPABASE_URL',
type: 'encrypted',
},
SUPABASE_SERVICE_ROLE_KEY: {
key: 'SUPABASE_SERVICE_ROLE_KEY',
type: 'encrypted',
},
NEXT_PUBLIC_SUPABASE_ANON_KEY: {
key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
type: 'encrypted',
},
NEXT_PUBLIC_SUPABASE_URL: {
key: 'NEXT_PUBLIC_SUPABASE_URL',
type: 'encrypted',
},
} as const
export const ENV_VAR_RAW_KEYS = Object.values(ENV_VAR_KEYS).map((x) => x.key)
@@ -0,0 +1,228 @@
import { useState } from 'react'
import { ENV_VAR_RAW_KEYS } from 'components/interfaces/Integrations/Integrations-Vercel.constants'
import { Markdown } from 'components/interfaces/Markdown'
import { vercelIcon } from 'components/to-be-cleaned/ListIcons'
import { useIntegrationConnectionsCreateMutation } from 'data/integrations/integration-connections-create-mutation'
import { VercelProjectsResponse } from 'data/integrations/integrations-vercel-projects-query'
import { IntegrationProjectConnection } from 'data/integrations/integrations.types'
import { useSelectedOrganization } from 'hooks'
import { BASE_PATH } from 'lib/constants'
import { Button, Listbox, cn } from 'ui'
interface Project {
id: string
name: string
ref: string
}
export interface ProjectLinkerProps {
organizationIntegrationId: string | undefined
foreignProjects: VercelProjectsResponse[]
supabaseProjects: Project[]
onCreateConnections?: () => void
installedConnections: IntegrationProjectConnection[] | undefined
setLoading?: (x: boolean) => void
}
const UNDEFINED_SELECT_VALUE = 'undefined'
const ProjectLinker = ({
organizationIntegrationId,
foreignProjects,
supabaseProjects,
onCreateConnections: _onCreateConnections,
installedConnections = [],
setLoading,
}: ProjectLinkerProps) => {
const selectedOrganization = useSelectedOrganization()
const [supabaseProjectRef, setSupabaseProjectRef] = useState(UNDEFINED_SELECT_VALUE)
const [vercelProjectId, setVercelProjectId] = useState(UNDEFINED_SELECT_VALUE)
const { mutate: createConnections, isLoading } = useIntegrationConnectionsCreateMutation({
onSuccess() {
_onCreateConnections?.()
},
onSettled() {
if (setLoading) setLoading(false)
},
})
function onCreateConnections() {
const projectDetails = foreignProjects.filter((x) => x.id === vercelProjectId)[0]
if (!organizationIntegrationId) {
console.error('No integration ID set')
return
}
if (setLoading) setLoading(true)
createConnections({
organizationIntegrationId,
connection: {
foreign_project_id: vercelProjectId,
supabase_project_ref: supabaseProjectRef,
metadata: {
...projectDetails,
supabaseConfig: {
projectEnvVars: {
write: true,
},
},
},
},
orgSlug: selectedOrganization?.slug,
})
}
const Panel = ({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={cn(
'flex flex-col gap-6 px-5 mx-auto w-full justify-center items-center',
className
)}
{...props}
>
{children}
</div>
)
}
// create a flat array of foreign project ids. ie, ["prj_MlkO6AiLG5ofS9ojKrkS3PhhlY3f", ..]
const flatInstalledConnectionsIds = new Set(installedConnections.map((x) => x.foreign_project_id))
// check that vercel project is not already installed
const filteredForeignProjects: VercelProjectsResponse[] = foreignProjects.filter(
(foreignProject) => {
return !flatInstalledConnectionsIds.has(foreignProject.id)
}
)
return (
<div className="flex flex-col gap-4">
<div className="relative border rounded-lg p-12 bg shadow">
<div
className="absolute inset-0 bg-grid-black/5 [mask-image:linear-gradient(0deg,#fff,rgba(255,255,255,0.6))] dark:bg-grid-white/5 dark:[mask-image:linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.5))]"
style={{ backgroundPosition: '10px 10px' }}
></div>
<div className="flex gap-0 w-full relative">
<Panel>
<div className="bg-white shadow border rounded p-1 w-12 h-12 flex justify-center items-center">
<img src={`${BASE_PATH}/img/supabase-logo.svg`} alt="Supabase" className="w-6" />
</div>
<Listbox
className="w-full"
value={supabaseProjectRef ?? UNDEFINED_SELECT_VALUE}
onChange={(e) => setSupabaseProjectRef(e)}
>
<Listbox.Option value={UNDEFINED_SELECT_VALUE} label="Choose a project" disabled>
Choose project
</Listbox.Option>
{supabaseProjects.map((project) => (
<Listbox.Option
key={project.id}
value={project.ref}
label={project.name}
addOnBefore={() => {
return (
<>
<div className="bg-white shadow border rounded p-1 w-6 h-6 flex justify-center items-center">
<img
src={`${BASE_PATH}/img/supabase-logo.svg`}
alt="Supabase"
className="w-4"
/>
</div>
</>
)
}}
>
{project.name}
</Listbox.Option>
))}
</Listbox>
</Panel>
<div className="border border-scale-1000 h-px w-16 border-dashed self-end mb-5"></div>
<Panel>
<div className="bg-black shadow rounded p-1 w-12 h-12 flex justify-center items-center">
{/* <img src="/img/icons/vercel.svg" style={{ height: 21 }} alt="integration icon" /> */}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="white"
viewBox="0 0 512 512"
className="w-6"
>
<path fill-rule="evenodd" d="M256,48,496,464H16Z" />
</svg>
</div>
<Listbox
className="w-full"
value={vercelProjectId ?? UNDEFINED_SELECT_VALUE}
onChange={(e) => setVercelProjectId(e)}
>
<Listbox.Option value={UNDEFINED_SELECT_VALUE} label="Choose a project" disabled>
Choose Vercel project
</Listbox.Option>
{filteredForeignProjects.map((project) => {
return (
<Listbox.Option
key={project.id}
value={project.id}
label={project.name}
addOnBefore={() => {
return (
<>
{!project?.framework ? (
vercelIcon
) : (
<img
src={`${BASE_PATH}/img/icons/frameworks/${project.framework}.svg`}
width={21}
height={21}
alt={`icon`}
/>
)}
</>
)
}}
>
{project.name}
</Listbox.Option>
)
})}
</Listbox>
</Panel>
</div>
</div>
<div className="flex w-full justify-end">
<Button
size="medium"
className="self-end"
onClick={onCreateConnections}
loading={isLoading}
disabled={isLoading}
>
Connect project
</Button>
</div>
<Markdown
content={`
The following environment variables will be added:
${ENV_VAR_RAW_KEYS.map((x, idx) => {
return `
\n
- \`${x}\`
`
})}
`}
/>
</div>
)
}
export default ProjectLinker
+18
View File
@@ -0,0 +1,18 @@
import { ReactMarkdownProps } from 'react-markdown/lib/complex-types'
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
import { cn } from 'ui'
interface Props extends Omit<ReactMarkdownProps, 'children' | 'node'> {
className?: string
content: string
}
const Markdown = ({ className, content, ...props }: Props) => {
return (
<ReactMarkdown {...props} className={cn('prose text-sm', className)}>
{content}
</ReactMarkdown>
)
}
export { Markdown }
@@ -13,6 +13,7 @@ import PaymentMethods from './PaymentMethods'
import ProjectsSummary from './ProjectsSummary'
import TaxID from './TaxID/TaxID'
import OrganizationBillingMigrationPanel from '../GeneralSettings/OrganizationBillingMigrationPanel'
import { ScaffoldContainer, ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
const BillingSettings = () => {
const { ui } = useStore()
@@ -101,36 +102,34 @@ const BillingSettings = () => {
}, [slug])
return (
<article className="container my-4 max-w-4xl space-y-8">
<div className="space-y-8">
{orgBillingMigrationEnabled && canMigrateOrganization && !subscription_id && (
<OrganizationBillingMigrationPanel />
)}
<ProjectsSummary projects={projects} />
<CreditBalance balance={balance} isCredit={isCredit} isDebt={isDebt} />
<PaymentMethods
loading={isLoadingCustomer || isLoadingPaymentMethods}
defaultPaymentMethod={defaultPaymentMethod}
paymentMethods={paymentMethods || []}
onDefaultMethodUpdated={setCustomer}
onPaymentMethodsDeleted={() => getPaymentMethods()}
onPaymentMethodAdded={() => getPaymentMethods()}
/>
<ScaffoldContainerLegacy>
{orgBillingMigrationEnabled && canMigrateOrganization && !subscription_id && (
<OrganizationBillingMigrationPanel />
)}
<ProjectsSummary projects={projects} />
<CreditBalance balance={balance} isCredit={isCredit} isDebt={isDebt} />
<PaymentMethods
loading={isLoadingCustomer || isLoadingPaymentMethods}
defaultPaymentMethod={defaultPaymentMethod}
paymentMethods={paymentMethods || []}
onDefaultMethodUpdated={setCustomer}
onPaymentMethodsDeleted={() => getPaymentMethods()}
onPaymentMethodAdded={() => getPaymentMethods()}
/>
<BillingEmail />
<BillingEmail />
<BillingAddress
loading={isLoadingCustomer}
address={customer?.address ?? {}}
onAddressUpdated={(address: any) => setCustomer({ ...customer, address })}
/>
<TaxID
loading={isLoadingTaxIds}
taxIds={taxIds || []}
onTaxIdsUpdated={(ids: any) => setTaxIds(ids)}
/>
</div>
</article>
<BillingAddress
loading={isLoadingCustomer}
address={customer?.address ?? {}}
onAddressUpdated={(address: any) => setCustomer({ ...customer, address })}
/>
<TaxID
loading={isLoadingTaxIds}
taxIds={taxIds || []}
onTaxIdsUpdated={(ids: any) => setTaxIds(ids)}
/>
</ScaffoldContainerLegacy>
)
}
@@ -1,39 +1,54 @@
import { ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold'
import BillingAddress from './BillingAddress/BillingAddress'
import BillingEmail from './BillingEmail'
import CostControl from './CostControl/CostControl'
import Subscription from './Subscription/Subscription'
import CreditBalance from './CreditBalance'
import { LayoutWrapper } from './Scaffold'
import BillingAddress from './BillingAddress/BillingAddress'
import TaxID from './TaxID/TaxID'
import PaymentMethods from './PaymentMethods/PaymentMethods'
// [Joshen TODO] Last one: payment method details management
// Either as part of the subscription plan component like project, or its own section like the old design
import Subscription from './Subscription/Subscription'
import TaxID from './TaxID/TaxID'
const BillingSettings = () => {
return (
<>
<LayoutWrapper>
<ScaffoldContainer>
<Subscription />
</LayoutWrapper>
<LayoutWrapper>
</ScaffoldContainer>
<ScaffoldDivider />
<ScaffoldContainer>
<CostControl />
</LayoutWrapper>
<LayoutWrapper>
</ScaffoldContainer>
<ScaffoldDivider />
<ScaffoldContainer>
<CreditBalance />
</LayoutWrapper>
<LayoutWrapper>
</ScaffoldContainer>
<ScaffoldDivider />
<ScaffoldContainer>
<PaymentMethods />
</LayoutWrapper>
<LayoutWrapper>
</ScaffoldContainer>
<ScaffoldDivider />
<ScaffoldContainer>
<BillingEmail />
</LayoutWrapper>
<LayoutWrapper>
</ScaffoldContainer>
<ScaffoldDivider />
<ScaffoldContainer>
<BillingAddress />
</LayoutWrapper>
<LayoutWrapper>
</ScaffoldContainer>
<ScaffoldDivider />
<ScaffoldContainer>
<TaxID />
</LayoutWrapper>
</ScaffoldContainer>
</>
)
}
@@ -1,5 +0,0 @@
import { PropsWithChildren } from 'react'
export const LayoutWrapper = ({ children }: PropsWithChildren<{}>) => (
<div className="border-b last:border-0">{children}</div>
)
@@ -19,6 +19,7 @@ import { useCheckPermissions, useFlag, useSelectedOrganization, useStore } from
import { patch } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import OrganizationDeletePanel from './OrganizationDeletePanel'
import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
const GeneralSettings = () => {
const queryClient = useQueryClient()
@@ -69,7 +70,7 @@ const GeneralSettings = () => {
}
return (
<div className="container my-4 max-w-4xl space-y-8">
<ScaffoldContainerLegacy>
<Form id={formId} initialValues={initialValues} onSubmit={onUpdateOrganization}>
{({ isSubmitting, handleReset, values, initialValues, resetForm }: any) => {
const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues)
@@ -190,7 +191,7 @@ const GeneralSettings = () => {
</Form>
{canDeleteOrganization && <OrganizationDeletePanel />}
</div>
</ScaffoldContainerLegacy>
)
}
@@ -0,0 +1,160 @@
import {
EmptyIntegrationConnection,
IntegrationConnection,
IntegrationConnectionHeader,
IntegrationInstallation,
} from 'components/interfaces/Integrations/IntegrationPanels'
import { Markdown } from 'components/interfaces/Markdown'
import {
ScaffoldContainer,
ScaffoldSection,
ScaffoldSectionContent,
ScaffoldSectionDetail,
} from 'components/layouts/Scaffold'
import { useIntegrationsVercelInstalledConnectionDeleteMutation } from 'data/integrations/integrations-vercel-installed-connection-delete-mutation'
import {
IntegrationProjectConnection,
Integration as TIntegration,
} from 'data/integrations/integrations.types'
import { useSelectedOrganization } from 'hooks'
import { BASE_PATH } from 'lib/constants'
import { pluralize } from 'lib/helpers'
import { EMPTY_ARR } from 'lib/void'
import { useGithubConnectionConfigPanelSnapshot } from 'state/github-connection-config-panel'
import { Button, Dropdown, IconChevronDown, IconRefreshCcw, IconTrash } from 'ui'
export interface IntegrationProps {
title: string
orgName?: string
description?: string
note?: string
detail?: string
integrations?: TIntegration[]
}
const Integration = ({
title,
orgName,
description,
note,
detail,
integrations = EMPTY_ARR,
}: IntegrationProps) => {
const snapshot = useGithubConnectionConfigPanelSnapshot()
const selectedOrganization = useSelectedOrganization()
const { mutate: deleteMutate, isLoading: isDeleteLoading } =
useIntegrationsVercelInstalledConnectionDeleteMutation()
const ConnectionHeading = ({ integration }: { integration: TIntegration }) => {
return (
<IntegrationConnectionHeader
markdown={`### ${integration.connections.length} project ${pluralize(
integration.connections.length,
'connection'
)}
Repository connections for ${title?.toLowerCase()}
`}
/>
)
}
// Call the mutation when needed
const handleDelete = (organizationIntegrationId: string, projectIntegrationId: string) => {
const variables = {
organization_integration_id: organizationIntegrationId,
id: projectIntegrationId,
orgSlug: selectedOrganization?.slug,
}
deleteMutate(variables)
}
return (
<>
<ScaffoldContainer>
<ScaffoldSection>
<ScaffoldSectionDetail>
{detail && <Markdown content={detail} />}
<img
className="border rounded-lg shadow w-48 mt-6 border-body"
src={`${BASE_PATH}/img/integrations/covers/${title.toLowerCase()}-cover.png?v=3`}
alt="cover"
/>
</ScaffoldSectionDetail>
<ScaffoldSectionContent>
<Markdown content={`${description}`} />
<div className="flex flex-col gap-12">
{integrations.length > 0 &&
integrations.map((integration, i) => {
return (
<div key={i}>
<IntegrationInstallation title={title} key={i} integration={integration} />
{integration.connections.length > 0 ? (
<>
<ConnectionHeading integration={integration} />
<ul className="flex flex-col">
{integration.connections.map(
(connection: IntegrationProjectConnection, i) => (
<IntegrationConnection
key={i}
connection={connection}
type={integration.integration.name}
actions={
<Dropdown
side="bottom"
align="end"
size="large"
overlay={
<>
<Dropdown.Item
icon={<IconTrash size={14} />}
onSelect={() =>
handleDelete(integration.id, connection.id)
}
>
Delete
</Dropdown.Item>
</>
}
>
<Button
asChild
iconRight={<IconChevronDown />}
type="default"
>
<span>Manage</span>
</Button>
</Dropdown>
}
/>
)
)}
</ul>
</>
) : (
<ConnectionHeading integration={integration} />
)}
<EmptyIntegrationConnection
onClick={() => {
snapshot.setOrganizationIntegrationId(integration.id)
snapshot.setOpen(true)
}}
>
Add new project connection
</EmptyIntegrationConnection>
</div>
)
})}
</div>
<Markdown content={`${note}`} className="text-scale-900" />
</ScaffoldSectionContent>
</ScaffoldSection>
</ScaffoldContainer>
</>
)
}
export default Integration
@@ -0,0 +1,79 @@
import { ScaffoldDivider } from 'components/layouts/Scaffold'
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import { useSelectedOrganization } from 'hooks'
import Integration from './Integration'
import SidePanelVercelProjectLinker from './SidePanelVercelProjectLinker'
const IntegrationSettings = () => {
const org = useSelectedOrganization()
const { data } = useOrgIntegrationsQuery({ orgSlug: org?.slug })
const vercelIntegrations = data?.filter(
(integration) => integration.integration.name === 'Vercel'
) // vercel
const githubIntegrations = data?.filter(
(integration) => integration.integration.name === 'GitHub'
) // github
vercelIntegrations?.map((x) => {
let data: any = { ...x }
const avatarSrc = data.metadata.account.avatar
? `https://vercel.com/api/www/avatar/${data.metadata.account.avatar}?s=48`
: `https://vercel.com/api/www/avatar?teamId=${data.metadata.account.team_id}&s=48`
data['metadata']['account']['avatar'] = avatarSrc
return data
})
return (
<>
<Integration
title="Vercel"
orgName={org?.name}
detail={`
## Vercel Integration
Connect your Vercel teams to your Supabase organization.
`}
description={`
### How does the Vercel integration work?
Supabase will keep the right environment variables up to date in each of the projects you assign to a Supabase project.
You can also link multiple Vercel Projects to the same Supabase project.
`}
note={`
Your Vercel connection has access to 7 Vercel Projects.
You can change the scope of the access for Supabase by configuring here.
`}
integrations={vercelIntegrations}
/>
<ScaffoldDivider />
<Integration
title="GitHub"
orgName={org?.name}
detail={`
## GitHub Connections
Connect any of your GitHub repositories to a project.
`}
description={`
GitHub connections are coming soon.
### How will GitHub connections work?
You will be able to connect a GitHub repository to a Supabase project.
The GitHub app will watch for changes in your repository such as file changes, branch changes as well as pull request activity.
These connections will be part of a GitHub workflow that is currently in deveopment.
`}
note={`
`}
integrations={githubIntegrations}
/>
<SidePanelVercelProjectLinker />
</>
)
}
export default IntegrationSettings
@@ -0,0 +1,58 @@
import { useParams } from 'common'
import { Markdown } from 'components/interfaces/Markdown'
// import { useIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import { useStore } from 'hooks'
import { observer } from 'mobx-react-lite'
import { useGithubConnectionConfigPanelSnapshot } from 'state/github-connection-config-panel'
import { IconGitHub, IconSearch, Input, Select, SidePanel } from 'ui'
// TO DO
const GitHubRepoSelection = () => {
const { ui } = useStore()
const { slug } = useParams()
// const { data } = useIntegrationsQuery({ orgSlug: slug })
const githubConnectionConfigPanelSnapshot = useGithubConnectionConfigPanelSnapshot()
return (
<SidePanel
header={'Add GitHub repository'}
size="large"
visible={false}
// visible={githubConnectionConfigPanelSnapshot.visible}
// onCancel={() => githubConnectionConfigPanelSnapshot.setVisible(false)}
>
<div className="my-10 flex flex-col gap-6">
<SidePanel.Content>
<Markdown
content={`
### Choose repository to connect to
Check the details below before proceeding
`}
/>
</SidePanel.Content>
<SidePanel.Content className="flex flex-col gap-2">
<label className="text-sm text-scale-900">GitHub scope / organisation</label>
<div className="flex gap-3 w-full">
<Select icon={<IconGitHub />} className="w-full">
<Select.Option value="mildtomato">MildTomato</Select.Option>
<Select.Option value="supabase">Supabase</Select.Option>
</Select>
<Input autoFocus icon={<IconSearch />} className="w-full" placeholder="Search..." />
</div>
</SidePanel.Content>
<SidePanel.Content>
<ul>
<li className="border px-10">
{/* <IntegrationConnectionOption connection={githubIntegrations[0]} /> */}
</li>
</ul>
</SidePanel.Content>
</div>
</SidePanel>
)
}
export default observer(GitHubRepoSelection)
@@ -0,0 +1,103 @@
import ProjectLinker from 'components/interfaces/Integrations/ProjectLinker'
import { Markdown } from 'components/interfaces/Markdown'
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import { useVercelProjectConnectionsQuery } from 'data/integrations/integrations-vercel-installed-connections-query'
import { useVercelProjectsQuery } from 'data/integrations/integrations-vercel-projects-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useSelectedOrganization } from 'hooks'
import { EMPTY_ARR } from 'lib/void'
import { useMemo } from 'react'
import { useGithubConnectionConfigPanelSnapshot } from 'state/github-connection-config-panel'
import { SidePanel } from 'ui'
const SidePanelVercelProjectLinker = () => {
const selectedOrganization = useSelectedOrganization()
const { data: integrationData } = useOrgIntegrationsQuery({
orgSlug: selectedOrganization?.slug,
})
const vercelIntegrations = integrationData?.filter(
(integration) => integration.integration.name === 'Vercel'
) // vercel
const snapshot = useGithubConnectionConfigPanelSnapshot()
/**
* Find the right integration
*
* we use the snapshot.organizationIntegrationId which should be set whenever this sidepanel is opened
*/
const selectedIntegration = vercelIntegrations?.find(
(x) => x.id === snapshot.organizationIntegrationId
)
const organizationIntegrationId = snapshot.organizationIntegrationId
const open = snapshot.open
/**
* Supabase projects available
*/
const { data: supabaseProjectsData } = useProjectsQuery({
enabled: organizationIntegrationId !== null,
})
const supabaseProjects = useMemo(
() =>
supabaseProjectsData
?.filter((project) => project.organization_id === selectedOrganization?.id)
.map((project) => ({ id: project.id.toString(), name: project.name, ref: project.ref })) ??
EMPTY_ARR,
[selectedOrganization?.id, supabaseProjectsData]
)
const { data: vercelProjectsData } = useVercelProjectsQuery(
{
organization_integration_id: organizationIntegrationId,
},
{ enabled: organizationIntegrationId !== null }
)
const vercelProjects = useMemo(() => vercelProjectsData ?? EMPTY_ARR, [vercelProjectsData])
return (
<SidePanel
header={'Add GitHub repository'}
size="large"
visible={open}
hideFooter
onCancel={() => snapshot.setOpen(false)}
>
<div className="py-10 flex flex-col gap-6 bg-body h-full">
<SidePanel.Content>
<Markdown
content={`
### Choose repository to connect to
Check the details below before proceeding
`}
/>
</SidePanel.Content>
<SidePanel.Content className="flex flex-col gap-2">
<ProjectLinker
organizationIntegrationId={selectedIntegration?.id}
foreignProjects={vercelProjects}
supabaseProjects={supabaseProjects}
onCreateConnections={() => {
snapshot.setOpen(false)
}}
installedConnections={selectedIntegration?.connections}
/>
</SidePanel.Content>
<SidePanel.Content>
<ul>
<li className="border px-10">
{/* <IntegrationConnectionOption connection={githubIntegrations[0]} /> */}
</li>
</ul>
</SidePanel.Content>
</div>
</SidePanel>
)
}
export default SidePanelVercelProjectLinker
@@ -10,6 +10,7 @@ import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
import { get, head } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { Button, IconChevronLeft, IconChevronRight, IconDownload, IconFileText, Loading } from 'ui'
import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
const PAGE_LIMIT = 10
@@ -93,7 +94,7 @@ const InvoicesSettings = () => {
}
return (
<div className="container my-4 max-w-4xl space-y-1">
<ScaffoldContainerLegacy>
<Loading active={loading}>
<Table
head={[
@@ -185,7 +186,7 @@ const InvoicesSettings = () => {
}
/>
</Loading>
</div>
</ScaffoldContainerLegacy>
)
}
@@ -13,6 +13,7 @@ import PublishAppModal from './PublishAppSidePanel'
import { AuthorizedApp, useAuthorizedAppsQuery } from 'data/oauth/authorized-apps-query'
import AuthorizedAppRow from './AuthorizedAppRow'
import RevokeAppModal from './RevokeAppModal'
import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
// [Joshen] Note on nav UX
// Kang Ming mentioned that it might be better to split Published Apps and Authorized Apps into 2 separate tabs
@@ -51,7 +52,7 @@ const OAuthApps = () => {
return (
<>
<div className="container my-4 max-w-5xl space-y-8">
<ScaffoldContainerLegacy>
<div>
<div className="flex items-center justify-between">
<div>
@@ -65,95 +66,93 @@ const OAuthApps = () => {
</Button>
</div>
<div className="mt-4">
{isLoadingPublishedApps && (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
{isLoadingPublishedApps && (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isErrorPublishedApps && (
<AlertError className="mt-4" subject="Unable to retrieve published OAuth apps" />
)}
{createdApp !== undefined && (
<Alert withIcon variant="success" title="Successfully published a new application!">
<div className="absolute top-4 right-4">
<Button
type="text"
icon={<IconX size={18} />}
className="px-1"
onClick={() => setCreatedApp(undefined)}
/>
</div>
)}
{isErrorPublishedApps && (
<AlertError subject="Unable to retrieve published OAuth apps" />
)}
{createdApp !== undefined && (
<Alert withIcon variant="success" title="Successfully published a new application!">
<div className="absolute top-4 right-4">
<Button
type="text"
icon={<IconX size={18} />}
className="px-1"
onClick={() => setCreatedApp(undefined)}
<div className="w-full space-y-4">
<p className="text-sm">
Ensure that you store the client secret securely - you will not be able to see it
again.
</p>
<div className="space-y-4">
<Input
copy
readOnly
size="small"
// layout="horizontal"
label="Client ID"
className="max-w-xl input-mono"
value={createdApp.client_id}
onChange={() => {}}
/>
<Input
copy
readOnly
size="small"
// layout="horizontal"
label="Client secret"
className="max-w-xl input-mono"
value={createdApp.client_secret}
onChange={() => {}}
/>
</div>
<div className="w-full space-y-4">
<p className="text-sm">
Ensure that you store the client secret securely - you will not be able to see
it again.
</p>
<div className="space-y-4">
<Input
copy
readOnly
size="small"
// layout="horizontal"
label="Client ID"
className="max-w-xl input-mono"
value={createdApp.client_id}
onChange={() => {}}
/>
<Input
copy
readOnly
size="small"
// layout="horizontal"
label="Client secret"
className="max-w-xl input-mono"
value={createdApp.client_secret}
onChange={() => {}}
/>
</div>
</div>
</Alert>
)}
</div>
</Alert>
)}
{isSuccessPublishedApps && (
<>
{(publishedApps?.length ?? 0) === 0 ? (
<div className="bg-scale-100 dark:bg-scale-300 border rounded p-4 flex items-center justify-between">
<p className="prose text-sm">You do not have any published applications yet</p>
</div>
) : (
<Table
className="mt-4"
head={[
<Table.th key="icon" className="w-[30px]"></Table.th>,
<Table.th key="name">Name</Table.th>,
<Table.th key="client-id">Client ID</Table.th>,
<Table.th key="client-secret">Client Secret</Table.th>,
<Table.th key="client-secret">Created at</Table.th>,
<Table.th key="delete-action"></Table.th>,
]}
body={
sortedPublishedApps?.map((app) => (
<OAuthAppRow
key={app.id}
app={app}
onSelectEdit={() => {
setShowPublishModal(true)
setSelectedAppToUpdate(app)
}}
onSelectDelete={() => setSelectedAppToDelete(app)}
/>
)) ?? []
}
/>
)}
</>
)}
</div>
{isSuccessPublishedApps && (
<>
{(publishedApps?.length ?? 0) === 0 ? (
<div className="bg-scale-100 dark:bg-scale-300 border rounded p-4 flex items-center justify-between mt-4">
<p className="prose text-sm">You do not have any published applications yet</p>
</div>
) : (
<Table
className="mt-4"
head={[
<Table.th key="icon" className="w-[30px]"></Table.th>,
<Table.th key="name">Name</Table.th>,
<Table.th key="client-id">Client ID</Table.th>,
<Table.th key="client-secret">Client Secret</Table.th>,
<Table.th key="client-secret">Created at</Table.th>,
<Table.th key="delete-action"></Table.th>,
]}
body={
sortedPublishedApps?.map((app) => (
<OAuthAppRow
key={app.id}
app={app}
onSelectEdit={() => {
setShowPublishModal(true)
setSelectedAppToUpdate(app)
}}
onSelectDelete={() => setSelectedAppToDelete(app)}
/>
)) ?? []
}
/>
)}
</>
)}
</div>
<div>
@@ -204,7 +203,7 @@ const OAuthApps = () => {
)}
</div>
</div>
</div>
</ScaffoldContainerLegacy>
<PublishAppModal
visible={showPublishModal}
@@ -11,6 +11,7 @@ import { Member, Role } from 'types'
import { Button, Form, IconMail, Input, Listbox, Modal } from 'ui'
export interface InviteMemberButtonProps {
orgId: number
userId: number
members: Member[]
roles: Role[]
@@ -18,6 +19,7 @@ export interface InviteMemberButtonProps {
}
const InviteMemberButton = ({
orgId,
userId,
members = [],
roles = [],
@@ -31,9 +33,15 @@ const InviteMemberButton = ({
const { permissions: allPermissions } = useGetPermissions()
const canInviteMembers = roles.some(({ id: role_id }) =>
doPermissionsCheck(allPermissions, PermissionAction.CREATE, 'user_invites', {
resource: { role_id },
})
doPermissionsCheck(
allPermissions,
PermissionAction.CREATE,
'user_invites',
{
resource: { role_id },
},
orgId
)
)
const initialValues = { email: '', role: '' }
@@ -111,7 +111,7 @@ const MembersView = ({ searchString }: MembersViewProps) => {
return (
<>
<div className="rounded">
<div className="rounded w-full">
<Loading active={!filteredMembers}>
<Table
head={[
@@ -14,6 +14,12 @@ import { useProfile } from 'lib/profile'
import InviteMemberButton from './InviteMemberButton'
import MembersView from './MembersView'
import { hasMultipleOwners, useGetRolesManagementPermissions } from './TeamSettings.utils'
import {
ScaffoldActionsGroup,
ScaffoldContainerLegacy,
ScaffoldFilterAndContent,
ScaffoldActionsContainer,
} from 'components/layouts/Scaffold'
const TeamSettings = () => {
const { ui } = useStore()
@@ -70,9 +76,9 @@ const TeamSettings = () => {
}
return (
<>
<div className="container my-4 max-w-4xl space-y-8">
<div className="flex justify-between">
<ScaffoldContainerLegacy>
<ScaffoldFilterAndContent>
<ScaffoldActionsContainer className="justify-between">
<Input
icon={<IconSearch size="tiny" />}
size="small"
@@ -82,10 +88,11 @@ const TeamSettings = () => {
id="email"
placeholder="Filter members"
/>
<div className="flex items-center space-x-4">
{canAddMembers && profile !== undefined && (
<ScaffoldActionsGroup>
{canAddMembers && profile !== undefined && selectedOrganization !== undefined && (
<div>
<InviteMemberButton
orgId={selectedOrganization.id}
userId={profile.id}
members={members}
roles={roles}
@@ -124,13 +131,11 @@ const TeamSettings = () => {
)}
</Tooltip.Root>
</div>
</div>
</div>
</div>
<div className="container my-4 max-w-4xl space-y-8">
</ScaffoldActionsGroup>
</ScaffoldActionsContainer>
<MembersView searchString={searchString} />
</div>
</>
</ScaffoldFilterAndContent>
</ScaffoldContainerLegacy>
)
}
@@ -1,20 +1,21 @@
import GeneralSettings from './GeneralSettings/GeneralSettings'
import TeamSettings from './TeamSettings/TeamSettings'
import BillingSettings from './BillingSettings/BillingSettings'
import BillingSettingsV2 from './BillingSettingsV2/BillingSettings'
import GeneralSettings from './GeneralSettings/GeneralSettings'
import IntegrationSettings from './IntegrationSettings/IntegrationSettings'
import InvoicesSettings from './InvoicesSettings/InvoicesSettings'
import NewOrgForm from './NewOrg/NewOrgForm'
import Usage from './Usage/Usage'
import OAuthApps from './OAuthApps/OAuthApps'
import BillingSettingsV2 from './BillingSettingsV2/BillingSettings'
import TeamSettings from './TeamSettings/TeamSettings'
export {
GeneralSettings,
TeamSettings,
BillingSettings,
BillingSettingsV2,
GeneralSettings,
IntegrationSettings,
InvoicesSettings,
NewOrgForm,
Usage,
OAuthApps,
TeamSettings,
Usage,
}
@@ -0,0 +1,78 @@
import { FC } from 'react'
import Link from 'next/link'
import { IconChevronRight, IconX, cn } from 'ui'
import { withAuth, useFlag } from 'hooks'
import { observer } from 'mobx-react-lite'
import { BASE_PATH } from 'lib/constants'
import React from 'react'
import { ScaffoldContainer } from './Scaffold'
import { useParams } from 'common'
const IntegrationWindowLayout: FC<any> = ({ organization, project, children }) => {
const ongoingIncident = useFlag('ongoingIncident')
const maxHeight = ongoingIncident ? 'calc(100vh - 44px)' : '100vh'
return (
<div className="flex w-full flex-col" style={{ height: maxHeight, maxHeight }}>
<Header organization={organization} project={project} />
{children}
</div>
)
}
const INTEGRATION_LAYOUT_MAX_WIDTH = '' // 'max-w-[720px]'
export default withAuth(observer(IntegrationWindowLayout))
export const IntegrationWindowLayoutWithoutAuth = observer(IntegrationWindowLayout)
const Header: FC<any> = ({ organization, project }) => {
let stepNumber = organization ? 1 : project ? 2 : 0
const { externalId } = useParams()
const title = externalId
? 'Supabase + Vercel Deploy Button'
: 'Supabase + Vercel Integration Marketplace Connector'
return (
<div className="bg">
<ScaffoldContainer className={cn('py-3 border-b', INTEGRATION_LAYOUT_MAX_WIDTH)}>
<div className="flex items-center gap-6 w-full">
<div className="flex gap-2 items-center">
<div className="bg-white shadow border rounded p-1 w-8 h-8 flex justify-center items-center">
<img src={`${BASE_PATH}/img/supabase-logo.svg`} alt="Supabase" className="w-4" />
</div>
<IconX className="text-scale-800" strokeWidth={2} size={16} />
<div className="bg-black shadow rounded p-1 w-8 h-8 flex justify-center items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="white"
viewBox="0 0 512 512"
className="w-4"
>
<path fill-rule="evenodd" d="M256,48,496,464H16Z" />
</svg>
</div>
</div>
<span className="text-sm" title={title}>
{title}
</span>
</div>
</ScaffoldContainer>
</div>
)
}
const maxWidthClasses = 'mx-auto w-full max-w-[1600px]'
const paddingClasses = 'px-6 lg:px-14 xl:px-28 2xl:px-32'
const IntegrationScaffoldContainer = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return <div ref={ref} {...props} className={cn(maxWidthClasses, paddingClasses, className)} />
})
IntegrationScaffoldContainer.displayName = 'IntegrationScaffoldContainer'
export { IntegrationScaffoldContainer }
@@ -1,15 +1,54 @@
import { useSelectedOrganization } from 'hooks'
import { useParams } from 'common'
import { useFlag, useSelectedOrganization } from 'hooks'
import { useRouter } from 'next/router'
import { PropsWithChildren } from 'react'
import AccountLayout from './AccountLayout/AccountLayout'
import { Tabs } from 'ui'
import { AccountLayout } from './'
import { ScaffoldContainer, ScaffoldDivider, ScaffoldHeader, ScaffoldTitle } from './Scaffold'
const OrganizationLayout = ({ children }: PropsWithChildren<{}>) => {
const selectedOrganization = useSelectedOrganization()
const router = useRouter()
const { slug } = useParams()
const id = router.asPath.split('/').at(-1)
const showOAuthApps = useFlag('oauthApps')
const showIntegrationsV2 = useFlag('integrationsV2')
const isOrgBilling = !!selectedOrganization?.subscription_id
return (
<AccountLayout
title={selectedOrganization?.name ?? 'Supabase'}
breadcrumbs={[{ key: `org-settings`, label: 'Settings' }]}
>
<ScaffoldHeader>
<ScaffoldContainer>
<ScaffoldTitle>{selectedOrganization?.name ?? 'Organization'} settings</ScaffoldTitle>
</ScaffoldContainer>
<ScaffoldContainer>
<nav>
<Tabs
listClassNames="border-none"
size="small"
type="underlined"
activeId={id}
onChange={(id: any) => {
router.push(`/org/${slug}/${id}`)
}}
>
<Tabs.Panel id="general" label="General" className="!my-0" />
<Tabs.Panel id="team" label="Team" className="!my-0" />
{showIntegrationsV2 && (
<Tabs.Panel id="integrations" label="Integrations" className="!my-0" />
)}
<Tabs.Panel id="billing" label="Billing" className="!my-0" />
{isOrgBilling && <Tabs.Panel id="usage" label="Usage" />}
<Tabs.Panel id="invoices" label="Invoices" className="!my-0" />
{showOAuthApps && <Tabs.Panel id="apps" label="OAuth Apps" className="!my-0" />}
</Tabs>
</nav>
</ScaffoldContainer>
</ScaffoldHeader>
<ScaffoldDivider />
{children}
</AccountLayout>
)
+129
View File
@@ -0,0 +1,129 @@
import React from 'react'
import { cn } from 'ui'
const maxWidthClasses = 'mx-auto w-full max-w-[1600px]'
const paddingClasses = 'px-6 lg:px-14 xl:px-28 2xl:px-32'
// lg: pt - 8
const ScaffoldHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
return (
<header {...props} ref={ref} className={cn('w-full', 'flex-col gap-3', className)}></header>
)
}
)
const ScaffoldTitle = React.forwardRef<
HTMLHeadingElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => {
return <h1 ref={ref} {...props} className={cn('text-3xl pt-6 pb-4', className)} />
})
const ScaffoldContainer = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
return <div ref={ref} {...props} className={cn(maxWidthClasses, paddingClasses, className)} />
}
)
const ScaffoldDivider = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
return <div ref={ref} {...props} className={cn('w-full h-px bg-scale-400', className)} />
}
)
const ScaffoldSection = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
return (
<div
ref={ref}
{...props}
className={cn('flex flex-col gap-3 py-6', 'lg:grid md:grid-cols-12 lg:py-12', className)}
/>
)
}
)
const ScaffoldSectionDetail = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return <div ref={ref} {...props} className={cn('col-span-4 xl:col-span-5', className)} />
})
const ScaffoldSectionContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
{...props}
className={cn('col-span-8 xl:col-span-7', 'flex flex-col gap-6', className)}
/>
)
})
// Table and filters
const ScaffoldFilterAndContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return <div ref={ref} {...props} className={cn('flex flex-col gap-3 items-center', className)} />
})
// Actions Group
const ScaffoldActionsContainer = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return <div ref={ref} {...props} className={cn('flex w-full items-center', className)} />
})
// Actions Group
const ScaffoldActionsGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
return <div ref={ref} {...props} className={cn('flex flex-row gap-3', className)} />
}
)
// For older layouts
const ScaffoldContainerLegacy = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
{...props}
className={cn(maxWidthClasses, paddingClasses, 'my-8 flex flex-col gap-8', className)}
/>
)
})
ScaffoldHeader.displayName = 'ScaffoldHeader'
ScaffoldTitle.displayName = 'ScaffoldTitle'
ScaffoldContainer.displayName = 'ScaffoldContainer'
ScaffoldDivider.displayName = 'ScaffoldDivider'
ScaffoldSection.displayName = 'ScaffoldSection'
ScaffoldSectionDetail.displayName = 'ScaffoldSectionDetail'
ScaffoldSectionContent.displayName = 'ScaffoldSectionContent'
ScaffoldFilterAndContent.displayName = 'ScaffoldFilterAndContent'
ScaffoldActionsContainer.displayName = 'ScaffoldActionsContainer'
ScaffoldActionsGroup.displayName = 'ScaffoldActionsGroup'
ScaffoldContainerLegacy.displayName = 'ScaffoldContainerLegacy'
export {
ScaffoldHeader,
ScaffoldTitle,
ScaffoldContainer,
ScaffoldDivider,
ScaffoldSection,
ScaffoldSectionDetail,
ScaffoldSectionContent,
ScaffoldFilterAndContent,
ScaffoldActionsContainer,
ScaffoldActionsGroup,
ScaffoldContainerLegacy,
}
@@ -2,9 +2,9 @@ import { IconDatabase } from 'ui'
import SVG from 'react-inlinesvg'
export const vercelIcon = (
<div className="flex items-center justify-center rounded bg-black p-1 dark:bg-white">
<div className="flex items-center justify-center rounded bg-black p-1">
<svg
className="m-auto fill-current text-white dark:text-black"
className="m-auto fill-current text-white"
width="12px"
height="12px"
viewBox="0 0 1155 1000"
+3 -1
View File
@@ -6,11 +6,12 @@ export interface AlertErrorProps {
ref?: string
subject?: string
error?: ResponseError | null
className?: string
}
// [Joshen] To standardize the language for all error UIs
const AlertError = ({ ref, subject, error }: AlertErrorProps) => {
const AlertError = ({ ref, subject, error, className }: AlertErrorProps) => {
const subjectString = subject?.replace(/ /g, '%20')
let href = `/support/new?category=dashboard_bug`
@@ -21,6 +22,7 @@ const AlertError = ({ ref, subject, error }: AlertErrorProps) => {
return (
<Alert
withIcon
className={className}
variant="warning"
title={subject}
actions={[
+115
View File
@@ -0,0 +1,115 @@
import { useIntegrationsQuery } from 'data/integrations/integrations-query'
import { IntegrationName } from 'data/integrations/integrations.types'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useState } from 'react'
import { Organization } from 'types'
import { Badge, IconHexagon, Listbox } from 'ui'
export interface OrganizationPickerProps {
label?: string
onSelectedOrgChange?: (org: Organization) => void
integrationName: IntegrationName
}
const OrganizationPicker = ({
label = 'Choose an organization',
onSelectedOrgChange,
integrationName,
}: OrganizationPickerProps) => {
const [selectedOrg, setSelectedOrg] = useState<Organization | null>(null)
/**
* array of integrations installed on all
*/
const { data: integrationData, isLoading: integrationDataLoading } = useIntegrationsQuery()
/**
* filter integrations to match integrationName
*/
const vercelIntegrationsInstalled = integrationData?.filter(
(integration) => integration.integration.name === 'Vercel'
)
const { data, isLoading } = useOrganizationsQuery({
onSuccess(organizations) {
const firstOrg = organizations?.[0]
if (firstOrg && selectedOrg === null) {
setSelectedOrg(firstOrg)
onSelectedOrgChange?.(firstOrg)
}
},
})
function _onSelectedOrgChange(slug: string) {
const org = data?.find((org) => org.slug === slug)
if (org) {
setSelectedOrg(org)
onSelectedOrgChange?.(org)
}
}
if (isLoading || integrationDataLoading) {
return (
<Listbox label={label} value="loading" disabled>
<Listbox.Option key="loading" value="loading" label="Loading...">
Loading...
</Listbox.Option>
</Listbox>
)
}
/**
* Organization type with `installationInstalled` added
*/
interface OrganizationsResponseWithInstalledData extends Organization {
installationInstalled?: boolean
}
/**
* A flat array of org slugs that have integration installed
*/
const flatInstalledConnectionsIds =
integrationData && integrationData.length > 0
? integrationData?.map((x) => x.organization.slug)
: []
/**
* Organizations with extra `installationInstalled` attribute
* Used to show label/badge and allow/disallow installing
*/
const organizationsWithInstalledData: OrganizationsResponseWithInstalledData[] = data
? data.map((org) => {
return {
...org,
installationInstalled: !flatInstalledConnectionsIds.includes(org.slug) ? true : false,
}
})
: []
return (
<Listbox label={label} value={selectedOrg?.slug} onChange={_onSelectedOrgChange}>
{organizationsWithInstalledData?.map((org) => {
const label = (
<div className="flex gap-3 items-center">
{org.name}
{org.installationInstalled && <Badge color="scale">Integration Installed</Badge>}
</div>
)
return (
<Listbox.Option
key={org.id}
value={org.slug}
// @ts-expect-error
label={label}
addOnBefore={({ active, selected }: any) => <IconHexagon />}
>
{label}
</Listbox.Option>
)
})}
</Listbox>
)
}
export default OrganizationPicker
@@ -0,0 +1,65 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { integrationKeys } from './keys'
export type IntegrationConnectionsCreateVariables = {
organizationIntegrationId: string
connection: {
foreign_project_id: string
supabase_project_ref: string
metadata: any
}
orgSlug: string | undefined
}
export async function createIntegrationConnections({
organizationIntegrationId,
connection,
}: IntegrationConnectionsCreateVariables) {
const response = await post(`${API_URL}/integrations/vercel/connections`, {
organization_integration_id: organizationIntegrationId,
connection,
})
if (response.error) {
throw response.error
}
return response
}
type IntegrationConnectionsCreateData = Awaited<ReturnType<typeof createIntegrationConnections>>
export const useIntegrationConnectionsCreateMutation = ({
onSuccess,
...options
}: Omit<
UseMutationOptions<
IntegrationConnectionsCreateData,
unknown,
IntegrationConnectionsCreateVariables
>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<
IntegrationConnectionsCreateData,
unknown,
IntegrationConnectionsCreateVariables
>((vars) => createIntegrationConnections(vars), {
async onSuccess(data, variables, context) {
await Promise.all([
queryClient.invalidateQueries(integrationKeys.integrationsList()),
queryClient.invalidateQueries(integrationKeys.integrationsListWithOrg(variables.orgSlug)),
queryClient.invalidateQueries(
integrationKeys.vercelProjectList(variables.organizationIntegrationId)
),
queryClient.invalidateQueries(
integrationKeys.vercelConnectionsList(variables.organizationIntegrationId)
),
])
await onSuccess?.(data, variables, context)
},
...options,
})
}
@@ -0,0 +1,137 @@
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { integrationKeys } from './keys'
// repos fetch
export type IntegrationsVariables = {
orgSlug?: string
}
export type IntegrationProjectConnection = {
id: string
createdAt: string
from: {
name: string
}
to: {
name: string
}
}
export type Integration = {
id: string
type: 'VERCEL' | 'NETLIFY' | 'GITHUB'
createdBy: string
createdAt: string
connections: IntegrationProjectConnection[]
metadata?: {
vercelTeam?: string
gitHubConnectionOwner?: string
}
}
export type IntegrationsResponse = Integration[]
export async function getIntegrations({ orgSlug }: IntegrationsVariables, signal?: AbortSignal) {
if (!orgSlug) {
throw new Error('orgSlug is required')
}
const response: IntegrationsResponse = [
{
id: '1',
type: 'VERCEL',
createdBy: 'alaister@supabase.io',
createdAt: '2023-06-05T06:56:25.565Z',
metadata: {
vercelTeam: 'alaister-team',
},
connections: [
{
id: '1.0',
createdAt: '2023-06-05T01:56:25.565Z',
to: {
name: 'www prod',
},
from: {
name: 'alaister project prod',
},
},
{
id: '1.1',
createdAt: '2023-06-14T06:56:25.565Z',
to: {
name: 'www staging',
},
from: {
name: 'alaister project staging',
},
},
{
id: '1.1',
createdAt: '2023-06-02T21:56:25.565Z',
to: {
name: 'bees knees',
},
from: {
name: 'jonny bee project',
},
},
],
},
{
id: '2',
type: 'VERCEL',
createdBy: 'alaister@supabase.io',
createdAt: '2023-06-05T06:56:25.565Z',
metadata: {
vercelTeam: 'alaister-team',
},
connections: [],
},
{
id: '3',
type: 'GITHUB',
createdBy: 'Alaister',
createdAt: '2023-06-05T06:56:25.565Z',
metadata: {
gitHubConnectionOwner: 'alaister',
},
connections: [
{
id: '1.2',
createdAt: '2023-06-05T06:56:25.565Z',
to: {
name: 'supabase/supabase',
},
from: {
name: 'alaister project',
},
},
],
},
]
// const response = await get(`${API_URL}/projects/${orgSlug}/integrations`, {
// signal,
// })
// if (response.error) {
// throw response.error
// }
return response as IntegrationsResponse
}
export type IntegrationsData = Awaited<ReturnType<typeof getIntegrations>>
export type IntegrationsError = unknown
export const useIntegrationsGitHubReposQuery = <TData = IntegrationsData>(
{ orgSlug }: IntegrationsVariables,
{ enabled = true, ...options }: UseQueryOptions<IntegrationsData, IntegrationsError, TData> = {}
) =>
useQuery<IntegrationsData, IntegrationsError, TData>(
integrationKeys.integrationsList(), // TODO: make a unique key for github repos
({ signal }) => getIntegrations({ orgSlug }, signal),
{ enabled: enabled && typeof orgSlug !== 'undefined', ...options }
)
@@ -0,0 +1,35 @@
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { integrationKeys } from './keys'
import { IntegrationsVariables, Integration } from './integrations.types'
export type IntegrationsResponse = Integration[]
export async function getIntegrations({ orgSlug }: IntegrationsVariables, signal?: AbortSignal) {
if (!orgSlug) {
throw new Error('orgSlug is required')
}
const response = await get(`${API_URL}/integrations/${orgSlug}?expand=true`, {
signal,
})
if (response.error) {
throw response.error
}
return response as IntegrationsResponse
}
export type IntegrationsData = Awaited<ReturnType<typeof getIntegrations>>
export type ProjectIntegrationConnectionsData = Awaited<ReturnType<typeof getIntegrations>>
export type IntegrationsError = unknown
export const useOrgIntegrationsQuery = <TData = IntegrationsData>(
{ orgSlug }: IntegrationsVariables,
{ enabled = true, ...options }: UseQueryOptions<IntegrationsData, IntegrationsError, TData> = {}
) =>
useQuery<IntegrationsData, IntegrationsError, TData>(
integrationKeys.integrationsListWithOrg(orgSlug),
({ signal }) => getIntegrations({ orgSlug }, signal),
{ enabled: enabled && typeof orgSlug !== 'undefined', ...options }
)
@@ -0,0 +1,32 @@
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { integrationKeys } from './keys'
import { IntegrationsVariables, Integration } from './integrations.types'
export type IntegrationsResponse = Integration[]
export async function getIntegrations({}: {}, signal?: AbortSignal) {
const response = await get(`${API_URL}/integrations`, {
signal,
})
if (response.error) {
throw response.error
}
return response as IntegrationsResponse
}
export type IntegrationsData = Awaited<ReturnType<typeof getIntegrations>>
export type ProjectIntegrationConnectionsData = Awaited<ReturnType<typeof getIntegrations>>
export type IntegrationsError = unknown
export const useIntegrationsQuery = <TData = IntegrationsData>({
enabled = true,
...options
}: UseQueryOptions<IntegrationsData, IntegrationsError, TData> = {}) =>
useQuery<IntegrationsData, IntegrationsError, TData>(
integrationKeys.integrationsList(),
({ signal }) => getIntegrations({}, signal),
{ enabled: enabled, ...options }
)
@@ -0,0 +1,58 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { delete_ } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { UserContent } from 'types'
import { integrationKeys } from './keys'
type DeleteVariables = {
id: string
organization_integration_id: string
orgSlug: string | undefined
}
export async function deleteConnection(
{ organization_integration_id, id }: DeleteVariables,
signal?: AbortSignal
) {
if (!organization_integration_id) {
throw new Error('organization_integration_id is required')
}
const response = await delete_<UserContent>(
`${API_URL}/integrations/vercel/connections/${id}`,
{ organization_integration_id },
{ signal }
)
if (response.error) throw response.error
return response
}
type DeleteContentData = Awaited<ReturnType<typeof deleteConnection>>
export const useIntegrationsVercelInstalledConnectionDeleteMutation = ({
onSuccess,
...options
}: Omit<UseMutationOptions<DeleteContentData, unknown, DeleteVariables>, 'mutationFn'> = {}) => {
const queryClient = useQueryClient()
return useMutation<DeleteContentData, unknown, DeleteVariables>(
(args) => deleteConnection(args),
{
async onSuccess(data, variables, context) {
console.log('variables in mutate delete onSuccess', variables)
await Promise.all([
queryClient.invalidateQueries(integrationKeys.integrationsList()),
queryClient.invalidateQueries(integrationKeys.integrationsListWithOrg(variables.orgSlug)),
queryClient.invalidateQueries(
integrationKeys.vercelProjectList(variables.organization_integration_id)
),
queryClient.invalidateQueries(
integrationKeys.vercelConnectionsList(variables.organization_integration_id)
),
])
await onSuccess?.(data, variables, context)
},
...options,
}
)
}
@@ -0,0 +1,90 @@
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { useCallback } from 'react'
import { integrationKeys } from './keys'
export type VercelProjectsVariables = {
organization_integration_id?: string
}
export type VercelProjectsResponse = {
id: string
name: string
metadata: {
id: string
name: string
framework: string
link: string
}
}
export async function getVercelInstalledConnections(
{ organization_integration_id }: VercelProjectsVariables,
signal?: AbortSignal
) {
if (!organization_integration_id) {
throw new Error('organization_integration_id is required')
}
const response = await get(
`${API_URL}/integrations/vercel/connections/${organization_integration_id}`,
{
signal,
}
)
if (response.error) {
throw response.error
}
return response as VercelProjectsResponse[]
}
export type VercelProjectsData = Awaited<ReturnType<typeof getVercelInstalledConnections>>
export type VercelProjectsError = unknown
export const useVercelProjectConnectionsQuery = <TData = VercelProjectsData>(
{ organization_integration_id }: VercelProjectsVariables,
{
enabled = true,
...options
}: UseQueryOptions<VercelProjectsData, VercelProjectsError, TData> = {}
) =>
useQuery<VercelProjectsData, VercelProjectsError, TData>(
integrationKeys.vercelConnectionsList(organization_integration_id),
({ signal }) => getVercelInstalledConnections({ organization_integration_id }, signal),
{
enabled:
enabled &&
typeof organization_integration_id !== 'undefined' &&
typeof organization_integration_id !== 'undefined',
...options,
}
)
/**
* useVercelProjectsPrefetch is used for prefetching data. For example, starting a query loading before a page is navigated to.
*
* @example
* const prefetch = useVercelProjectsPrefetch({ orgSlug })
*
* return (
* <Link onMouseEnter={() => prefetch()}>
* Start loading on hover
* </Link>
* )
*/
export const useVercelProjectsPrefetch = ({
organization_integration_id,
}: VercelProjectsVariables) => {
const client = useQueryClient()
return useCallback(() => {
if (organization_integration_id) {
client.prefetchQuery(
integrationKeys.vercelConnectionsList(organization_integration_id),
({ signal }) => getVercelInstalledConnections({ organization_integration_id }, signal)
)
}
}, [organization_integration_id])
}
@@ -0,0 +1,91 @@
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { useCallback } from 'react'
import { integrationKeys } from './keys'
import { VercelFramework } from './integrations.types'
export type VercelProjectsVariables = {
organization_integration_id: string | undefined
}
export type VercelProjectsResponse = {
id: string
name: string
framework: VercelFramework
metadata: {
id: string
name: string
framework: string
link: string
}
}
export async function getVercelProjects(
{ organization_integration_id }: VercelProjectsVariables,
signal?: AbortSignal
) {
if (!organization_integration_id) {
throw new Error('organization_integration_id is required')
}
const response = await get(
`${API_URL}/integrations/vercel/projects/${organization_integration_id}`,
{
signal,
}
)
if (response.error) {
throw response.error
}
return response as VercelProjectsResponse[]
}
export type VercelProjectsData = Awaited<ReturnType<typeof getVercelProjects>>
export type VercelProjectsError = unknown
export const useVercelProjectsQuery = <TData = VercelProjectsData>(
{ organization_integration_id }: VercelProjectsVariables,
{
enabled = true,
...options
}: UseQueryOptions<VercelProjectsData, VercelProjectsError, TData> = {}
) =>
useQuery<VercelProjectsData, VercelProjectsError, TData>(
integrationKeys.vercelProjectList(organization_integration_id),
({ signal }) => getVercelProjects({ organization_integration_id }, signal),
{
enabled: enabled && typeof organization_integration_id !== 'undefined',
...options,
}
)
/**
* useVercelProjectsPrefetch is used for prefetching data. For example, starting a query loading before a page is navigated to.
*
* @example
* const prefetch = useVercelProjectsPrefetch({ orgSlug })
*
* return (
* <Link onMouseEnter={() => prefetch()}>
* Start loading on hover
* </Link>
* )
*/
export const useVercelProjectsPrefetch = ({
// orgSlug,
// orgId,
organization_integration_id,
}: VercelProjectsVariables) => {
const client = useQueryClient()
return useCallback(() => {
if (organization_integration_id) {
client.prefetchQuery(
integrationKeys.vercelProjectList(organization_integration_id),
({ signal }) => getVercelProjects({ organization_integration_id }, signal)
)
}
}, [organization_integration_id])
}
@@ -0,0 +1,187 @@
export type VercelFramework =
| (
| 'blitzjs'
| 'nextjs'
| 'gatsby'
| 'remix'
| 'astro'
| 'hexo'
| 'eleventy'
| 'docusaurus-2'
| 'docusaurus'
| 'preact'
| 'solidstart'
| 'dojo'
| 'ember'
| 'vue'
| 'scully'
| 'ionic-angular'
| 'angular'
| 'polymer'
| 'svelte'
| 'sveltekit'
| 'sveltekit-1'
| 'ionic-react'
| 'create-react-app'
| 'gridsome'
| 'umijs'
| 'sapper'
| 'saber'
| 'stencil'
| 'nuxtjs'
| 'redwoodjs'
| 'hugo'
| 'jekyll'
| 'brunch'
| 'middleman'
| 'zola'
| 'hydrogen'
| 'vite'
| 'vitepress'
| 'vuepress'
| 'parcel'
| 'sanity'
| 'storybook'
)
| null
export type VercelGitLink =
| {
/**
* GitHub link
*/
org?: string
repo?: string
repoId?: number
type?: 'github'
createdAt?: number
deployHooks: {
createdAt?: number
id: string
name: string
ref: string
url: string
}[]
gitCredentialId?: string
updatedAt?: number
sourceless?: boolean
productionBranch?: string
}
| {
/**
* GitLab link
*/
projectId?: string
projectName?: string
projectNameWithNamespace?: string
projectNamespace?: string
projectUrl?: string
type?: 'gitlab'
createdAt?: number
deployHooks: {
createdAt?: number
id: string
name: string
ref: string
url: string
}[]
gitCredentialId?: string
updatedAt?: number
sourceless?: boolean
productionBranch?: string
}
| {
/**
* Bitbucket link
*/
name?: string
slug?: string
owner?: string
type?: 'bitbucket'
uuid?: string
workspaceUuid?: string
createdAt?: number
deployHooks: {
createdAt?: number
id: string
name: string
ref: string
url: string
}[]
gitCredentialId?: string
updatedAt?: number
sourceless?: boolean
productionBranch?: string
}
export type Imetadata = {
id: string
supabaseConfig: {
projectEnvVars: {
write: boolean
}
}
link?: VercelGitLink
name: string
framework: VercelFramework
}
export type IntegrationProjectConnection = {
id: string
inserted_at: string
updated_at: string
added_by: addedBy
supabase_project_ref: string
foreign_project_id: string
organization_integration_id: string
metadata: Imetadata
}
export type IntegrationsVariables = {
orgSlug?: string
}
export type IntegrationProjectConnectionPayload = {
foreignProjectId: string
supabaseProjectId: string
integrationId: string
metadata: Imetadata
}
export type userDetails = {
username: string
id: string
primary_email: string
}
type addedBy = userDetails
type updatedBy = userDetails
export type IntegrationName = 'Vercel' | 'Netlify' | 'GitHub'
export type VercelAcccountType = 'team' | 'personal'
export type VercelSource = 'marketplace' | 'deploy button'
export type Integration = {
id: string
integration: {
name: IntegrationName
}
added_by: addedBy
inserted_at: string
updated_at: string
connections: IntegrationProjectConnection[] | []
organization: {
slug: string
}
metadata?: {
vercelTeam?: string
gitHubConnectionOwner?: string
account: {
name: string
type: VercelAcccountType
avatar: string
source: VercelSource
owner_id: string
}
configuration_id: string
}
}
+9
View File
@@ -0,0 +1,9 @@
export const integrationKeys = {
integrationsListWithOrg: (orgSlug: string | undefined) =>
['organizations', orgSlug, 'integrations'] as const,
integrationsList: () => ['organizations', 'integrations'] as const,
vercelProjectList: (organization_integration_id: string | undefined) =>
['organizations', organization_integration_id, 'vercel-projects'] as const,
vercelConnectionsList: (organization_integration_id: string | undefined) =>
['organizations', organization_integration_id, 'vercel-connections'] as const,
}
@@ -0,0 +1,65 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { integrationKeys } from './keys'
export type VercelIntegrationCreateVariables = {
code: string
configurationId: string
orgSlug: string
metadata: { [key: string]: string }
source: string
// teamId is only present when a team is being installed
// personal accounts (hobby) will not have this value defined
teamId?: string
}
export async function createVercelIntegration({
code,
configurationId,
orgSlug,
metadata,
source,
teamId,
}: VercelIntegrationCreateVariables) {
const response = await post(`${API_URL}/integrations/vercel`, {
code,
configuration_id: configurationId,
organization_slug: orgSlug,
metadata,
source,
teamId,
})
if (response.error) {
throw response.error
}
return response
}
type VercelIntegrationCreateData = Awaited<ReturnType<typeof createVercelIntegration>>
export const useVercelIntegrationCreateMutation = ({
onSuccess,
...options
}: Omit<
UseMutationOptions<VercelIntegrationCreateData, unknown, VercelIntegrationCreateVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
console.log('mutating')
return useMutation<VercelIntegrationCreateData, unknown, VercelIntegrationCreateVariables>(
(vars) => createVercelIntegration(vars),
{
async onSuccess(data, variables, context) {
await Promise.all([
queryClient.invalidateQueries(integrationKeys.integrationsList()),
queryClient.invalidateQueries(integrationKeys.integrationsListWithOrg(variables.orgSlug)),
queryClient.invalidateQueries(integrationKeys.vercelProjectList(data.id)),
])
await onSuccess?.(data, variables, context)
},
...options,
}
)
}
@@ -0,0 +1,63 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { post } from 'lib/common/fetch'
import { API_URL, PRICING_TIER_PRODUCT_IDS, PROVIDERS } from 'lib/constants'
import { ProjectBase } from 'types'
import { projectKeys } from './keys'
export type ProjectCreateVariables = {
name: string
organizationId: number
dbPass: string
dbRegion: string
dbSql?: string
configurationId?: string
}
export async function createProject({
name,
organizationId,
dbPass,
dbRegion,
dbSql,
configurationId,
}: ProjectCreateVariables) {
const response = await post(`${API_URL}/projects`, {
cloud_provider: PROVIDERS.AWS.id, // hardcoded for DB instances to be under AWS
org_id: organizationId,
name,
db_pass: dbPass,
db_region: dbRegion,
db_sql: dbSql,
db_pricing_tier_id: PRICING_TIER_PRODUCT_IDS.FREE,
// auth_site_url: _store.selectedVercelProjectUrl,
vercel_configuration_id: configurationId,
})
if (response.error) {
throw response.error
}
return response as ProjectBase
}
type ProjectCreateData = Awaited<ReturnType<typeof createProject>>
export const useProjectCreateMutation = ({
onSuccess,
...options
}: Omit<
UseMutationOptions<ProjectCreateData, unknown, ProjectCreateVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<ProjectCreateData, unknown, ProjectCreateVariables>(
(vars) => createProject(vars),
{
async onSuccess(data, variables, context) {
await queryClient.invalidateQueries(projectKeys.list()),
await onSuccess?.(data, variables, context)
},
...options,
}
)
}
+1 -3
View File
@@ -19,9 +19,7 @@ export function usePushNext() {
// vercel integration
const isVercelIntegration = next.includes('https://vercel.com')
if (isVercelIntegration) {
searchParams.delete('returnTo')
return await router.push(`/vercel/integrate?${searchParams.toString()}`)
return
}
// database.new integration
+7
View File
@@ -247,6 +247,13 @@ export const detectOS = () => {
}
}
/**
* Pluralize a word based on a count
*/
export function pluralize(count: number, singular: string, plural?: string) {
return count === 1 ? singular : plural || singular + 's'
}
export const isValidHttpUrl = (value: string) => {
let url: URL
try {
+115
View File
@@ -0,0 +1,115 @@
import { ResponseFailure } from 'types'
type SupaResponse<T> = T | ResponseFailure
async function fetchGitHub<T = any>(url: string, responseJson = true): Promise<SupaResponse<T>> {
const response = await fetch(url)
if (!response.ok) {
return {
error: {
code: response.status,
message: response.statusText,
requestId: '',
},
}
}
try {
return (responseJson ? await response.json() : await response.text()) as T
} catch (error: any) {
return {
error: {
message: error.message,
code: 500,
requestId: '',
},
}
}
}
export type File = {
name: string
download_url: string
}
/**
* Returns the initial migration SQL from a GitHub repo.
* @param externalId An external GitHub URL for example: https://github.com/vercel/next.js/tree/canary/examples/with-supabase
*/
export async function getInitialMigrationSQLFromGitHubRepo(
externalId?: string
): Promise<string | null> {
if (!externalId) return null
const [, , , owner, repo, , branch, ...pathSegments] = externalId?.split('/') ?? []
const path = pathSegments.join('/')
const baseGitHubUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`
const supabaseFolderUrl = `${baseGitHubUrl}/supabase?ref=${branch}`
const supabaseMigrationsPath = `supabase/migrations` // TODO: read this from the `supabase/config.toml` file
const migrationsFolderUrl = `${baseGitHubUrl}/${supabaseMigrationsPath}?ref=${branch}`
const [supabaseFilesResponse, migrationFilesResponse] = await Promise.all([
fetchGitHub<File[]>(supabaseFolderUrl),
fetchGitHub<File[]>(migrationsFolderUrl),
])
if ('error' in supabaseFilesResponse) {
console.warn(`Failed to fetch supabase files from GitHub: ${supabaseFilesResponse.error}`)
return null
}
if ('error' in migrationFilesResponse) {
console.warn(`Failed to fetch migration files from GitHub: ${migrationFilesResponse.error}`)
return null
}
const seedFileUrl = supabaseFilesResponse.find((file) => file.name === 'seed.sql')?.download_url
const sortedFiles = migrationFilesResponse.sort((a, b) => {
// sort by name ascending
if (a.name < b.name) return -1
if (a.name > b.name) return 1
return 0
})
const migrationFileDownloadUrlPromises = sortedFiles.map((file) =>
fetchGitHub<string>(file.download_url, false)
)
const [seedFileResponse, ...migrationFileResponses] = await Promise.all([
seedFileUrl ? fetchGitHub<string>(seedFileUrl, false) : Promise.resolve<string>(''),
...migrationFileDownloadUrlPromises,
])
const migrations = migrationFileResponses
.filter((response) => !(typeof response === 'object' && 'error' in response))
.join(';')
const seed =
typeof seedFileResponse === 'object' && 'error' in seedFileResponse ? '' : seedFileResponse
const migrationsTableSql = /* SQL */ `
create schema if not exists supabase_migrations;
create table if not exists supabase_migrations.schema_migrations (
version text not null primary key,
statements text[],
name text
);
${sortedFiles.map((file, i) => {
const migration = migrationFileResponses[i]
if (!migration || (typeof migration === 'object' && 'error' in migration)) return ''
const version = file.name.split('_')[0]
const statements = JSON.stringify(
migration
.split(';')
.map((statement) => statement.trim())
.filter(Boolean)
)
return /* SQL */ `
insert into supabase_migrations.schema_migrations (version, statements, name)
select '${version}', array_agg(jsonb_statements)::text[], '${file.name}'
from jsonb_array_elements_text($statements$${statements}$statements$::jsonb) as jsonb_statements;
`
})}
`
return `${migrations};${migrationsTableSql};${seed}`
}
+7
View File
@@ -0,0 +1,7 @@
// Because {} !== {} in JS, we need a shared object instances for comparison
export const EMPTY_OBJ = {}
export const EMPTY_ARR = []
export function noop() {}
+1 -1
View File
@@ -201,7 +201,7 @@ const nextConfig = {
]
},
images: {
domains: ['github.com'],
domains: ['github.com', 'api-frameworks.vercel.sh', 'vercel.com'],
},
// Ref: https://nextjs.org/docs/advanced-features/output-file-tracing#caveats
experimental: {
+19
View File
@@ -0,0 +1,19 @@
import { useEffect } from 'react'
import IntegrationWindowLayout from 'components/layouts/IntegrationWindowLayout'
import { useRouter } from 'next/router'
import { NextPageWithLayout } from 'types'
const VercelIntegration: NextPageWithLayout = () => {
const router = useRouter()
useEffect(() => {
router.push({ pathname: '/integrations/vercel/install', query: router.query })
}, [router])
return <></>
}
VercelIntegration.getLayout = (page) => <IntegrationWindowLayout>{page}</IntegrationWindowLayout>
export default VercelIntegration
@@ -0,0 +1,380 @@
import { debounce } from 'lodash'
import { useRouter } from 'next/router'
import { ChangeEvent, useRef, useState } from 'react'
import { useParams } from 'common'
import { Markdown } from 'components/interfaces/Markdown'
import IntegrationWindowLayout from 'components/layouts/IntegrationWindowLayout'
import { ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold'
import PasswordStrengthBar from 'components/ui/PasswordStrengthBar'
import { useProjectApiQuery } from 'data/config/project-api-query'
import { useIntegrationConnectionsCreateMutation } from 'data/integrations/integration-connections-create-mutation'
import { useIntegrationsQuery } from 'data/integrations/integrations-query'
import { useVercelProjectsQuery } from 'data/integrations/integrations-vercel-projects-query'
import { Integration } from 'data/integrations/integrations.types'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectCreateMutation } from 'data/projects/project-create-mutation'
import generator from 'generate-password'
import { useSelectedOrganization, useStore } from 'hooks'
import { AWS_REGIONS, DEFAULT_MINIMUM_PASSWORD_STRENGTH, PROVIDERS } from 'lib/constants'
import { passwordStrength } from 'lib/helpers'
import { getInitialMigrationSQLFromGitHubRepo } from 'lib/integration-utils'
import { NextPageWithLayout, ProjectBase } from 'types'
import { Alert, Button, Checkbox, IconBook, IconLifeBuoy, Input, Listbox, LoadingLine } from 'ui'
const VercelIntegration: NextPageWithLayout = () => {
const [loading, setLoading] = useState<boolean>(false)
return (
<>
<main className="overflow-auto flex flex-col h-full">
<LoadingLine loading={loading} />
<>
<ScaffoldContainer className="max-w-md flex flex-col gap-6 grow py-8">
<h1 className="text-xl text-scale-1200">New project</h1>
<>
<Markdown content={`Choose the Supabase Organization you wish to install to`} />
<CreateProject loading={loading} setLoading={setLoading} />
</>
</ScaffoldContainer>
<ScaffoldContainer className="flex flex-col gap-6 py-3">
<Alert withIcon variant="info" title="You can uninstall this Integration at any time.">
<Markdown
content={`You can remove this integration at any time either via Vercel or the Supabase dashboard.`}
/>
</Alert>
</ScaffoldContainer>
</>
<ScaffoldDivider />
</main>
<ScaffoldContainer className="bg-body flex flex-row gap-6 py-6 border-t">
<div className="flex items-center gap-2 text-xs text-scale-900">
<IconBook size={16} /> Docs
</div>
<div className="flex items-center gap-2 text-xs text-scale-900">
<IconLifeBuoy size={16} /> Support
</div>
</ScaffoldContainer>
</>
)
}
VercelIntegration.getLayout = (page) => <IntegrationWindowLayout>{page}</IntegrationWindowLayout>
const CreateProject = ({
loading,
setLoading,
}: {
loading: boolean
setLoading: (e: boolean) => void
}) => {
const router = useRouter()
const { ui } = useStore()
const selectedOrganization = useSelectedOrganization()
const [projectName, setProjectName] = useState('')
const [dbPass, setDbPass] = useState('')
const [passwordStrengthMessage, setPasswordStrengthMessage] = useState('')
const [passwordStrengthScore, setPasswordStrengthScore] = useState(-1)
const [shouldRunMigrations, setShouldRunMigrations] = useState(true)
const [dbRegion, setDbRegion] = useState(PROVIDERS.AWS.default_region)
// const [loading, setLoading] = useState(false)
const delayedCheckPasswordStrength = useRef(
debounce((value: string) => checkPasswordStrength(value), 300)
).current
const {
slug,
configurationId,
next,
currentProjectId: foreignProjectId,
externalId,
} = useParams()
const { mutateAsync: createConnections, isLoading: isLoadingCreateConnections } =
useIntegrationConnectionsCreateMutation({})
const { data: organizationData, isLoading: isLoadingOrganizationsQuery } = useOrganizationsQuery()
const organization = organizationData?.find((x) => x.slug === slug)
/**
* array of integrations installed
*/
const { data: integrationData, isLoading: integrationDataLoading } = useIntegrationsQuery()
/**
* the vercel integration installed for organization chosen
*/
const organizationIntegration: Integration | undefined = integrationData?.find(
(x) => x.organization.slug === slug
)
/**
* Vercel projects available for this integration
*/
const { data: vercelProjects } = useVercelProjectsQuery(
{
organization_integration_id: organizationIntegration?.id,
},
{ enabled: organizationIntegration !== undefined }
)
const canSubmit =
projectName != '' &&
passwordStrengthScore >= DEFAULT_MINIMUM_PASSWORD_STRENGTH &&
dbRegion != undefined
function onProjectNameChange(e: ChangeEvent<HTMLInputElement>) {
e.target.value = e.target.value.replace(/\./g, '')
setProjectName(e.target.value)
}
function onDbPassChange(e: ChangeEvent<HTMLInputElement>) {
const value = e.target.value
setDbPass(value)
if (value == '') {
setPasswordStrengthScore(-1)
setPasswordStrengthMessage('')
} else delayedCheckPasswordStrength(value)
}
async function checkPasswordStrength(value: string) {
const { message, strength } = await passwordStrength(value)
setPasswordStrengthScore(strength)
setPasswordStrengthMessage(message)
}
function generateStrongPassword() {
const password = generator.generate({
length: 16,
numbers: true,
uppercase: true,
})
setDbPass(password)
delayedCheckPasswordStrength(password)
}
const { mutateAsync: createProject } = useProjectCreateMutation()
const [newProjectRef, setNewProjectRef] = useState<string | undefined>(undefined)
async function onCreateProject() {
if (!organizationIntegration) {
console.error('No organization installation details found')
}
if (!organizationIntegration?.id) {
console.error('No organization installation ID found')
return
}
if (!foreignProjectId) {
console.error('No foreignProjectId ID set')
return
}
if (!configurationId) {
console.error('No configurationId ID set')
return
}
setLoading(true)
try {
if (!organization) {
throw new Error('No organization set')
}
let dbSql: string | undefined
if (shouldRunMigrations) {
const id = ui.setNotification({
category: 'info',
message: `Fetching initial migrations from GitHub repo`,
})
dbSql = (await getInitialMigrationSQLFromGitHubRepo(externalId)) ?? undefined
ui.setNotification({
id,
category: 'success',
message: `Done fetching initial migrations`,
})
}
let project: ProjectBase
try {
project = await createProject({
organizationId: organization.id,
name: projectName,
dbPass,
dbRegion,
dbSql,
configurationId,
})
setNewProjectRef(project.ref)
} catch (error: any) {
setLoading(false)
ui.setNotification({
category: 'error',
message: `Failed to create project: ${error.message}`,
})
return
}
} catch (error) {
console.error('Error', error)
setLoading(false)
}
}
const isInstallingRef = useRef(false)
// Wait for the new project to be created before creating the connection
useProjectApiQuery(
{ projectRef: newProjectRef },
{
enabled: newProjectRef !== undefined,
// refetch until the project is created
refetchInterval: (data) => {
return (data?.autoApiService.service_api_keys.length ?? 0) > 0 ? false : 1000
},
async onSuccess(data) {
const isReady = data.autoApiService.service_api_keys.length > 0
if (
!isReady ||
!organizationIntegration ||
!foreignProjectId ||
!newProjectRef ||
isInstallingRef.current
) {
return
}
isInstallingRef.current = true
const projectDetails = vercelProjects?.find((x) => x.id === foreignProjectId)
// Wrap the createConnections function call in a try-catch block
try {
await createConnections({
organizationIntegrationId: organizationIntegration?.id,
connection: {
foreign_project_id: foreignProjectId,
supabase_project_ref: newProjectRef,
metadata: {
...projectDetails,
supabaseConfig: {
projectEnvVars: {
write: true,
},
},
},
},
orgSlug: selectedOrganization?.slug,
})
} catch (error) {
console.error('An error occurred during createConnections:', error)
return
}
setLoading(false)
if (next) {
window.location.href = next
}
},
}
)
return (
<div className="">
<p className="mb-2">Project details for integration</p>
<div className="py-2">
<Input
autoFocus
id="projectName"
label="Project name"
type="text"
placeholder=""
descriptionText=""
value={projectName}
onChange={onProjectNameChange}
/>
</div>
<div className="py-2">
<Input
id="dbPass"
label="Database Password"
type="password"
placeholder="Type in a strong password"
value={dbPass}
copy={dbPass.length > 0}
onChange={onDbPassChange}
descriptionText={
<PasswordStrengthBar
passwordStrengthScore={passwordStrengthScore}
password={dbPass}
passwordStrengthMessage={passwordStrengthMessage}
generateStrongPassword={generateStrongPassword}
/>
}
/>
</div>
<div className="py-2">
<div className="mt-1">
<Listbox
label="Region"
type="select"
value={dbRegion}
onChange={(region) => setDbRegion(region)}
descriptionText="Select a region close to your users for the best performance."
>
{Object.keys(AWS_REGIONS).map((option: string, i) => {
const label = Object.values(AWS_REGIONS)[i]
return (
<Listbox.Option
key={option}
label={label}
value={label}
addOnBefore={({ active, selected }: any) => (
<img
alt="region icon"
className="w-5 rounded-sm"
src={`${router.basePath}/img/regions/${Object.keys(AWS_REGIONS)[i]}.svg`}
/>
)}
>
<span className="text-scale-1200">{label}</span>
</Listbox.Option>
)
})}
</Listbox>
</div>
</div>
<div className="py-2 pb-4">
<Checkbox
name="shouldRunMigrations"
label="Run migrations & seed.sql"
description="If your repository has migrations under supabase/migrations, they will be run automatically."
checked={shouldRunMigrations}
onChange={(e) => setShouldRunMigrations(e.target.checked)}
/>
</div>
<div className="flex flex-row w-full justify-end">
<Button
size="medium"
className="self-end"
disabled={loading}
loading={loading}
onClick={onCreateProject}
>
Create Project
</Button>
</div>
</div>
)
}
export default VercelIntegration
@@ -0,0 +1,108 @@
import { useMemo, useState } from 'react'
import { useParams } from 'common'
import ProjectLinker from 'components/interfaces/Integrations/ProjectLinker'
import { Markdown } from 'components/interfaces/Markdown'
import IntegrationWindowLayout from 'components/layouts/IntegrationWindowLayout'
import { ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold'
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import { useVercelProjectsQuery } from 'data/integrations/integrations-vercel-projects-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { EMPTY_ARR } from 'lib/void'
import { NextPageWithLayout, Organization } from 'types'
import { IconBook, IconLifeBuoy, LoadingLine } from 'ui'
const VercelIntegration: NextPageWithLayout = () => {
const { slug, configurationId, next, organizationSlug } = useParams()
const [loading, setLoading] = useState<boolean>(false)
/**
* Fetch the list of organization based integration installations for Vercel.
*
* Array of integrations installed on all
*/
const { data: integrationData, isLoading: integrationDataLoading } = useOrgIntegrationsQuery({
orgSlug: slug,
})
const { data, isLoading: isLoadingOrganizationsQuery } = useOrganizationsQuery({
enabled: slug !== undefined,
})
const organization = data?.find((organization: Organization) => organization.slug === slug)
const integration = integrationData?.find((x) => x.metadata?.configuration_id === configurationId)
const { data: supabaseProjectsData } = useProjectsQuery({
enabled: integration?.id !== undefined,
})
const supabaseProjects = useMemo(
() =>
supabaseProjectsData
?.filter((project) => project.organization_id === organization?.id)
.map((project) => ({ id: project.id.toString(), name: project.name, ref: project.ref })) ??
EMPTY_ARR,
[organizationSlug, supabaseProjectsData]
)
const { data: vercelProjectsData } = useVercelProjectsQuery(
{
organization_integration_id: integration?.id,
},
{ enabled: integration?.id !== undefined }
)
const vercelProjects = useMemo(() => vercelProjectsData ?? EMPTY_ARR, [vercelProjectsData])
return (
<>
<main className="overflow-auto flex flex-col h-full">
<LoadingLine loading={loading} />
<>
<ScaffoldContainer className="flex flex-col gap-6 grow py-8">
<header>
<h1 className="text-xl text-scale-1200">
Link a Supabase project to a Vercel project
</h1>
<Markdown
className="text-scale-900"
content={`
This Supabase integration manages your envioemnt variables automatically to provide the latest keys in the unlikely event that you will need to refresh your JWT token.
`}
/>
</header>
<ProjectLinker
organizationIntegrationId={integration?.id}
foreignProjects={vercelProjects}
supabaseProjects={supabaseProjects}
onCreateConnections={() => {
if (next) {
window.location.href = next
}
}}
installedConnections={integration?.connections}
setLoading={setLoading}
/>
</ScaffoldContainer>
</>
<ScaffoldDivider />
</main>
<ScaffoldContainer className="bg-body flex flex-row gap-6 py-6 border-t">
<div className="flex items-center gap-2 text-xs text-scale-900">
<IconBook size={16} /> Docs
</div>
<div className="flex items-center gap-2 text-xs text-scale-900">
<IconLifeBuoy size={16} /> Support
</div>
</ScaffoldContainer>
</>
)
}
VercelIntegration.getLayout = (page) => <IntegrationWindowLayout>{page}</IntegrationWindowLayout>
export default VercelIntegration
@@ -0,0 +1,292 @@
import { useParams } from 'common'
import { Markdown } from 'components/interfaces/Markdown'
import IntegrationWindowLayout from 'components/layouts/IntegrationWindowLayout'
import { ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold'
import { useIntegrationsQuery } from 'data/integrations/integrations-query'
import { IntegrationName } from 'data/integrations/integrations.types'
import { useVercelIntegrationCreateMutation } from 'data/integrations/vercel-integration-create-mutation'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useStore } from 'hooks'
import { useRouter } from 'next/router'
import { useState } from 'react'
import { useVercelIntegrationInstallationState } from 'state/vercel-integration-installation'
import { NextPageWithLayout, Organization } from 'types'
import { Alert, Badge, Button, IconBook, IconHexagon, IconLifeBuoy, Listbox, LoadingLine } from 'ui'
/**
* Organization type with `installationInstalled` added
*/
interface OrganizationsResponseWithInstalledData extends Organization {
installationInstalled?: boolean
}
/**
* Variations of the Vercel integration flow.
* They require different UI and logic.
*
* Deploy Button - the flow that starts from the Deploy Button - https://vercel.com/docs/integrations#deploy-button
* Marketplace - the flow that starts from the Marketplace - https://vercel.com/integrations
*
*/
export type VercelIntegrationFlow = 'deploy-button' | 'marketing'
const VercelIntegration: NextPageWithLayout = () => {
const router = useRouter()
const { ui } = useStore()
const { code, configurationId, next, teamId, source, externalId } = useParams()
const [selectedOrg, setSelectedOrg] = useState<Organization | null>(null)
const [organizationIntegrationId, setOrganizationIntegrationId] = useState<string | null>(null)
const snapshot = useVercelIntegrationInstallationState()
const flow: VercelIntegrationFlow = externalId ? 'marketing' : 'deploy-button'
/**
* Fetch the list of organization based integration installations for Vercel.
*
* Array of integrations installed on all
*/
const { data: integrationData, isLoading: integrationDataLoading } = useIntegrationsQuery()
const { data: organizationsData, isLoading: isLoadingOrganizationsQuery } = useOrganizationsQuery(
{
onSuccess(organizations) {
const firstOrg = organizations?.[0]
if (firstOrg && selectedOrg === null) {
setSelectedOrg(firstOrg)
snapshot.setSelectedOrganizationSlug(firstOrg.slug)
router.query.organizationSlug = firstOrg.slug
}
},
}
)
/**
* Flat array of org slugs that have integration installed
*
*/
const flatInstalledConnectionsIds: string[] | [] =
integrationData && integrationData.length > 0
? integrationData?.map((x) => x.organization.slug)
: []
/**
* Organizations with extra `installationInstalled` attribute
*
* Used to show label/badge and allow/disallow installing
*
*/
const organizationsWithInstalledData: OrganizationsResponseWithInstalledData[] = organizationsData
? organizationsData.map((org) => {
return {
...org,
installationInstalled: flatInstalledConnectionsIds.includes(org.slug) ? true : false,
}
})
: []
const { mutate, isLoading: isLoadingVercelIntegrationCreateMutation } =
useVercelIntegrationCreateMutation({
onSuccess({ id }) {
const orgSlug = selectedOrg?.slug
setOrganizationIntegrationId(id)
if (externalId) {
router.push({
pathname: `/integrations/vercel/${orgSlug}/deploy-button/new-project`,
query: router.query,
})
} else {
router.push({
pathname: `/integrations/vercel/${orgSlug}/marketplace/choose-project`,
query: router.query,
})
}
},
onError(error: any) {
ui.setNotification({
category: 'error',
message: `Creating Vercel integration failed: ${error.message}`,
})
},
})
function onInstall() {
const orgSlug = selectedOrg?.slug
const isIntegrationInstalled = organizationsWithInstalledData.some(
(x) => x.slug === orgSlug && x.installationInstalled
)
if (!orgSlug) {
return ui.setNotification({ category: 'error', message: 'Please select an organization' })
}
if (!code) {
return ui.setNotification({ category: 'error', message: 'Vercel code missing' })
}
if (!configurationId) {
return ui.setNotification({ category: 'error', message: 'Vercel Configuration ID missing' })
}
if (!source) {
return ui.setNotification({
category: 'error',
message: 'Vercel Configuration source missing',
})
}
/**
* Only install if instgration hasn't already been installed
*/
if (!isIntegrationInstalled) {
mutate({
code,
configurationId,
orgSlug,
metadata: {},
source,
teamId: teamId,
})
}
}
const dataLoading = isLoadingVercelIntegrationCreateMutation || isLoadingOrganizationsQuery
return (
<>
<main className="overflow-auto flex flex-col h-full">
<LoadingLine loading={isLoadingVercelIntegrationCreateMutation} />
{organizationIntegrationId === null && (
<>
<ScaffoldContainer className="max-w-md flex flex-col gap-6 grow py-8">
<h1 className="text-xl text-scale-1200">Choose organization</h1>
<>
<Markdown content={`Choose the Supabase Organization you wish to install to`} />
<OrganizationPicker
integrationName="Vercel"
organizationsWithInstalledData={organizationsWithInstalledData}
onSelectedOrgChange={(e) => {
router.query.organizationSlug = e.slug
setSelectedOrg(e)
}}
dataLoading={dataLoading}
/>
<div className="flex flex-row w-full justify-end">
<Button
size="medium"
className="self-end"
disabled={isLoadingVercelIntegrationCreateMutation}
loading={isLoadingVercelIntegrationCreateMutation}
onClick={onInstall}
>
Install integration
</Button>
</div>
</>
</ScaffoldContainer>
<ScaffoldContainer className="flex flex-col gap-6 py-3">
<Alert
withIcon
variant="info"
title="You can uninstall this Integration at any time."
>
<Markdown
content={`Remove this integration at any time either via Vercel or the Supabase dashboard.`}
/>
</Alert>
</ScaffoldContainer>
</>
)}
<ScaffoldDivider />
</main>
<ScaffoldContainer className="bg-body flex flex-row gap-6 py-6 border-t">
<div className="flex items-center gap-2 text-xs text-scale-900">
<IconBook size={16} /> Docs
</div>
<div className="flex items-center gap-2 text-xs text-scale-900">
<IconLifeBuoy size={16} /> Support
</div>
</ScaffoldContainer>
</>
)
}
VercelIntegration.getLayout = (page) => <IntegrationWindowLayout>{page}</IntegrationWindowLayout>
export interface OrganizationPickerProps {
label?: string
onSelectedOrgChange?: (org: Organization) => void
integrationName: IntegrationName
dataLoading: boolean
organizationsWithInstalledData: OrganizationsResponseWithInstalledData[]
}
const OrganizationPicker = ({
label = 'Choose an organization',
onSelectedOrgChange,
integrationName,
dataLoading,
organizationsWithInstalledData,
}: OrganizationPickerProps) => {
const { ui } = useStore()
const [selectedOrg, setSelectedOrg] = useState<Organization | null>(null)
const { data, isLoading } = useOrganizationsQuery({
onSuccess(organizations) {
const firstOrg = organizations?.[0]
if (firstOrg && selectedOrg === null) {
setSelectedOrg(firstOrg)
onSelectedOrgChange?.(firstOrg)
}
},
})
function _onSelectedOrgChange(slug: string) {
const org = data?.find((org) => org.slug === slug)
if (org) {
setSelectedOrg(org)
onSelectedOrgChange?.(org)
}
}
if (dataLoading) {
return (
<Listbox label={label} value="loading" disabled>
<Listbox.Option key="loading" value="loading" label="Loading...">
Loading...
</Listbox.Option>
</Listbox>
)
}
return (
<Listbox label={label} value={selectedOrg?.slug} onChange={_onSelectedOrgChange}>
{organizationsWithInstalledData?.map((org) => {
const label = (
<div className="flex gap-3 items-center">
{org.name}
{org.installationInstalled && <Badge color="scale">Integration Installed</Badge>}
</div>
)
return (
<Listbox.Option
key={org.id}
value={org.slug}
// @ts-expect-error
label={label}
addOnBefore={({ active, selected }: any) => <IconHexagon />}
>
{label}
</Listbox.Option>
)
})}
</Listbox>
)
}
export default VercelIntegration
+6 -37
View File
@@ -1,8 +1,3 @@
import { observer } from 'mobx-react-lite'
import { useRouter } from 'next/router'
import { Tabs } from 'ui'
import { useParams } from 'common/hooks'
import { OAuthApps } from 'components/interfaces/Organization'
import { OrganizationLayout } from 'components/layouts'
import Loading from 'components/ui/Loading'
@@ -11,47 +6,21 @@ import { useSelectedOrganization } from 'hooks'
import { NextPageWithLayout } from 'types'
const OrgOAuthApps: NextPageWithLayout = () => {
const { slug } = useParams()
const router = useRouter()
const selectedOrganization = useSelectedOrganization()
const { isLoading: isLoadingPermissions } = usePermissionsQuery()
// const { isLoading: isLoadingPermissions } = usePermissionsQuery()
const { data: permissions } = usePermissionsQuery()
return (
<>
{selectedOrganization === undefined && isLoadingPermissions ? (
{selectedOrganization === undefined && (permissions ?? []).length === 0 ? (
<Loading />
) : (
<div className="p-4 pt-0">
<div className="space-y-3">
<section className="mt-4">
<h1 className="text-3xl">{selectedOrganization?.name ?? 'Organization'} settings</h1>
</section>
<nav>
<Tabs
size="small"
type="underlined"
activeId="apps"
onChange={(id: any) => {
if (id !== 'apps') router.push(`/org/${slug}/${id}`)
}}
>
<Tabs.Panel id="general" label="General" />
<Tabs.Panel id="team" label="Team" />
<Tabs.Panel id="billing" label="Billing" />
<Tabs.Panel id="invoices" label="Invoices" />
<Tabs.Panel id="apps" label="OAuth Apps" />
</Tabs>
</nav>
</div>
<div className="mb-8">
<OAuthApps />
</div>
</div>
<OAuthApps />
)}
</>
)
}
OrgOAuthApps.getLayout = (page) => <OrganizationLayout>{page}</OrganizationLayout>
export default observer(OrgOAuthApps)
export default OrgOAuthApps
+11 -45
View File
@@ -1,22 +1,13 @@
import { useRouter } from 'next/router'
import { useParams } from 'common/hooks'
import { BillingSettings, BillingSettingsV2 } from 'components/interfaces/Organization'
import { OrganizationLayout } from 'components/layouts'
import Loading from 'components/ui/Loading'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { useFlag, useSelectedOrganization } from 'hooks'
import { useSelectedOrganization } from 'hooks'
import { NextPageWithLayout } from 'types'
import { Tabs } from 'ui'
const OrgBillingSettings: NextPageWithLayout = () => {
const { slug } = useParams()
const router = useRouter()
const selectedOrganization = useSelectedOrganization()
const { isLoading: isLoadingPermissions } = usePermissionsQuery()
const showOAuthApps = useFlag('oauthApps')
const selectedOrganization = useSelectedOrganization()
const isOrgBilling = !!selectedOrganization?.subscription_id
return (
@@ -24,40 +15,15 @@ const OrgBillingSettings: NextPageWithLayout = () => {
{selectedOrganization === undefined && isLoadingPermissions ? (
<Loading />
) : (
<div>
<div className="space-y-3.5">
<section className="mt-4 px-4">
<h1 className="text-3xl">{selectedOrganization?.name ?? 'Organization'} settings</h1>
</section>
<nav>
<Tabs
size="small"
type="underlined"
activeId="billing"
onChange={(id: any) => {
if (id !== 'billing') router.push(`/org/${slug}/${id}`)
}}
>
<Tabs.Panel id="general" label="General" />
<Tabs.Panel id="team" label="Team" />
<Tabs.Panel id="billing" label="Billing" className="!m-0" />
{isOrgBilling && <Tabs.Panel id="usage" label="Usage" />}
<Tabs.Panel id="invoices" label="Invoices" />
{showOAuthApps && <Tabs.Panel id="apps" label="OAuth Apps" />}
</Tabs>
</nav>
</div>
<div className="mb-8">
{isOrgBilling ? (
<BillingSettingsV2 />
) : (
<div className="px-4">
<BillingSettings />
</div>
)}
</div>
</div>
<>
{isOrgBilling ? (
<BillingSettingsV2 />
) : (
<div className="px-4">
<BillingSettings />
</div>
)}
</>
)}
</>
)
+2 -35
View File
@@ -1,53 +1,20 @@
import { useRouter } from 'next/router'
import { useParams } from 'common/hooks'
import { GeneralSettings } from 'components/interfaces/Organization'
import { OrganizationLayout } from 'components/layouts'
import Loading from 'components/ui/Loading'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { useFlag, useSelectedOrganization } from 'hooks'
import { useSelectedOrganization } from 'hooks'
import { NextPageWithLayout } from 'types'
import { Tabs } from 'ui'
const OrgGeneralSettings: NextPageWithLayout = () => {
const { data: permissions } = usePermissionsQuery()
const selectedOrganization = useSelectedOrganization()
const { slug } = useParams()
const router = useRouter()
const showOAuthApps = useFlag('oauthApps')
return (
<>
{selectedOrganization === undefined && (permissions ?? []).length === 0 ? (
<Loading />
) : (
<div className="p-4 pt-0">
<div className="space-y-3">
<section className="mt-4">
<h1 className="text-3xl">{selectedOrganization?.name ?? 'Organization'} settings</h1>
</section>
<nav>
<Tabs
size="small"
type="underlined"
activeId="general"
onChange={(id: any) => {
if (id !== 'general') router.push(`/org/${slug}/${id}`)
}}
>
<Tabs.Panel id="general" label="General" />
<Tabs.Panel id="team" label="Team" />
<Tabs.Panel id="billing" label="Billing" />
<Tabs.Panel id="invoices" label="Invoices" />
{showOAuthApps && <Tabs.Panel id="apps" label="OAuth Apps" />}
</Tabs>
</nav>
</div>
<div className="mb-8">
<GeneralSettings />
</div>
</div>
<GeneralSettings />
)}
</>
)
+11
View File
@@ -0,0 +1,11 @@
import { NextPageWithLayout } from 'types'
import { OrganizationLayout } from 'components/layouts'
import { IntegrationSettings } from 'components/interfaces/Organization'
const OrgIntegrationSettings: NextPageWithLayout = () => {
return <IntegrationSettings />
}
OrgIntegrationSettings.getLayout = (page) => <OrganizationLayout>{page}</OrganizationLayout>
export default OrgIntegrationSettings
+4 -37
View File
@@ -1,58 +1,25 @@
import { observer } from 'mobx-react-lite'
import { useRouter } from 'next/router'
import { Tabs } from 'ui'
import { useParams } from 'common/hooks'
import { InvoicesSettings } from 'components/interfaces/Organization'
import { OrganizationLayout } from 'components/layouts'
import Loading from 'components/ui/Loading'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { useFlag, useSelectedOrganization } from 'hooks'
import { useSelectedOrganization } from 'hooks'
import { NextPageWithLayout } from 'types'
const OrgInvoices: NextPageWithLayout = () => {
const { data: permissions } = usePermissionsQuery()
const selectedOrganization = useSelectedOrganization()
const { slug } = useParams()
const router = useRouter()
const showOAuthApps = useFlag('oauthApps')
return (
<>
{selectedOrganization === undefined && (permissions ?? []).length === 0 ? (
<Loading />
) : (
<div className="p-4 pt-0">
<div className="space-y-3">
<section className="mt-4">
<h1 className="text-3xl">{selectedOrganization?.name ?? 'Organization'} settings</h1>
</section>
<nav>
<Tabs
size="small"
type="underlined"
activeId="invoices"
onChange={(id: any) => {
if (id !== 'invoices') router.push(`/org/${slug}/${id}`)
}}
>
<Tabs.Panel id="general" label="General" />
<Tabs.Panel id="team" label="Team" />
<Tabs.Panel id="billing" label="Billing" />
<Tabs.Panel id="invoices" label="Invoices" />
{showOAuthApps && <Tabs.Panel id="apps" label="OAuth Apps" />}
</Tabs>
</nav>
</div>
<div className="mb-8">
<InvoicesSettings />
</div>
</div>
<InvoicesSettings />
)}
</>
)
}
OrgInvoices.getLayout = (page) => <OrganizationLayout>{page}</OrganizationLayout>
export default observer(OrgInvoices)
export default OrgInvoices
+2 -35
View File
@@ -1,53 +1,20 @@
import { useRouter } from 'next/router'
import { useParams } from 'common/hooks'
import { TeamSettings } from 'components/interfaces/Organization'
import { OrganizationLayout } from 'components/layouts'
import Loading from 'components/ui/Loading'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { useFlag, useSelectedOrganization } from 'hooks'
import { useSelectedOrganization } from 'hooks'
import { NextPageWithLayout } from 'types'
import { Tabs } from 'ui'
const OrgTeamSettings: NextPageWithLayout = () => {
const { data: permissions } = usePermissionsQuery()
const selectedOrganization = useSelectedOrganization()
const { slug } = useParams()
const router = useRouter()
const showOAuthApps = useFlag('oauthApps')
return (
<>
{selectedOrganization === undefined && (permissions ?? []).length === 0 ? (
<Loading />
) : (
<div className="p-4 pt-0">
<div className="space-y-3">
<section className="mt-4">
<h1 className="text-3xl">{selectedOrganization?.name ?? 'Organization'} settings</h1>
</section>
<nav>
<Tabs
size="small"
type="underlined"
activeId="team"
onChange={(id: any) => {
if (id !== 'team') router.push(`/org/${slug}/${id}`)
}}
>
<Tabs.Panel id="general" label="General" />
<Tabs.Panel id="team" label="Team" />
<Tabs.Panel id="billing" label="Billing" />
<Tabs.Panel id="invoices" label="Invoices" />
{showOAuthApps && <Tabs.Panel id="apps" label="OAuth Apps" />}
</Tabs>
</nav>
</div>
<div className="mb-8">
<TeamSettings />
</div>
</div>
<TeamSettings />
)}
</>
)
@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 1L3 8.636l3.203 28.313L24 47l17.797-10.051L45 8.636 24 1z" fill="#DA0B36"/><path d="M24 1v5.106-.023V47l17.797-10.051L45 8.636 24 1z" fill="#C10933"/><path d="M24.022 6L11 36h4.855l2.618-6.713h11.054L32.145 36H37L24.022 6zm3.804 19.15H20.22l3.803-9.403 3.804 9.402z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 374 B

@@ -0,0 +1,15 @@
<svg width="1280" height="1280" viewBox="0 0 1280 1280" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M815.039 94.6439C824.758 106.709 829.714 122.99 839.626 155.553L1056.17 866.901C976.107 825.368 889.072 795.413 797.281 779.252L656.29 302.798C653.983 295.002 646.822 289.654 638.693 289.654C630.542 289.654 623.368 295.03 621.08 302.853L481.795 779.011C389.579 795.1 302.146 825.109 221.741 866.793L439.347 155.388L439.348 155.388C449.291 122.882 454.262 106.629 463.982 94.5853C472.562 83.9531 483.723 75.6958 496.4 70.6002C510.76 64.8284 527.756 64.8284 561.749 64.8284H717.174C751.212 64.8284 768.23 64.8284 782.603 70.6123C795.292 75.7184 806.459 83.9923 815.039 94.6439Z" fill="url(#paint0_linear_709_110)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M840.951 900.754C805.253 931.279 734.002 952.097 651.929 952.097C551.197 952.097 466.767 920.737 444.363 878.561C436.354 902.732 434.558 930.396 434.558 948.068C434.558 948.068 429.281 1034.84 489.636 1095.2C489.636 1063.86 515.042 1038.46 546.381 1038.46C600.097 1038.46 600.036 1085.32 599.987 1123.34C599.986 1124.48 599.984 1125.61 599.984 1126.73C599.984 1184.44 635.255 1233.91 685.416 1254.77C677.924 1239.36 673.721 1222.05 673.721 1203.77C673.721 1148.73 706.034 1128.23 743.588 1104.41L743.588 1104.41C773.469 1085.46 806.668 1064.41 829.548 1022.17C841.486 1000.13 848.265 974.893 848.265 948.068C848.265 931.573 845.702 915.676 840.951 900.754Z" fill="#FF5D01"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M840.951 900.754C805.253 931.279 734.002 952.097 651.929 952.097C551.197 952.097 466.767 920.737 444.363 878.561C436.354 902.732 434.558 930.396 434.558 948.068C434.558 948.068 429.281 1034.84 489.636 1095.2C489.636 1063.86 515.042 1038.46 546.381 1038.46C600.097 1038.46 600.036 1085.32 599.987 1123.34C599.986 1124.48 599.984 1125.61 599.984 1126.73C599.984 1184.44 635.255 1233.91 685.416 1254.77C677.924 1239.36 673.721 1222.05 673.721 1203.77C673.721 1148.73 706.034 1128.23 743.588 1104.41L743.588 1104.41C773.469 1085.46 806.668 1064.41 829.548 1022.17C841.486 1000.13 848.265 974.893 848.265 948.068C848.265 931.573 845.702 915.676 840.951 900.754Z" fill="url(#paint1_linear_709_110)"/>
<defs>
<linearGradient id="paint0_linear_709_110" x1="882.997" y1="27.1132" x2="638.955" y2="866.902" gradientUnits="userSpaceOnUse">
<stop stop-color="#000014"/>
<stop offset="1" stop-color="#150426"/>
</linearGradient>
<linearGradient id="paint1_linear_709_110" x1="1001.68" y1="652.45" x2="790.326" y2="1094.91" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF1639"/>
<stop offset="1" stop-color="#FF1639" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,30 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M95.4242 249.857H173.991C203.89 249.857 232.049 263.909 250.026 287.799L327.526 390.789C328.991 392.736 329.212 395.349 328.095 397.513L283.421 484.069C281.278 488.221 275.532 488.71 272.719 484.978L95.4242 249.857Z" fill="url(#paint0_linear)"/>
<g filter="url(#filter0_d)">
<path d="M404.558 249.991H325.991C296.093 249.991 267.933 235.939 249.956 212.049L172.456 109.059C170.991 107.112 170.771 104.499 171.888 102.335L216.561 15.7794C218.705 11.6267 224.45 11.1382 227.264 14.8695L404.558 249.991Z" fill="url(#paint1_linear)"/>
</g>
</g>
<defs>
<filter id="filter0_d" x="71.1812" y="-39.6553" width="433.377" height="437.646" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="48"/>
<feGaussianBlur stdDeviation="50"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.270588 0 0 0 0 0 0 0 0 0 0.615686 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<linearGradient id="paint0_linear" x1="163.936" y1="392.775" x2="316.429" y2="155.244" gradientUnits="userSpaceOnUse">
<stop stop-color="#6700EB"/>
<stop offset="1" stop-color="#45009D"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="336.047" y1="107.073" x2="183.554" y2="344.604" gradientUnits="userSpaceOnUse">
<stop stop-color="#6700EB"/>
<stop offset="1" stop-color="#45009D"/>
</linearGradient>
<clipPath id="clip0">
<rect width="500" height="500" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="49" height="48" fill="none">
<path fill="#3F894A" d="M14.52 35.93a3.39 3.39 0 0 0-1.14-2.41 4.65 4.65 0 0 0-.1-.1l-.02-.01a7.6 7.6 0 0 1-2.14-5.4c0-3.08 1.37-11.87 1.67-11.87.23 0 .23 4.24.08 8.22-.1 2.77.3 3.86.96 3.86.88 0 1.02-2.79 1.11-5.92.08-2.57.3-6.14.48-6.16.28 0 .51 3.55.59 6.13.1 3.2.32 5.87 1.05 5.87.77 0 1-1.23.95-3.97-.07-3.34-.07-8.03.17-8.03.3 0 1.3 3.09 1.54 11.88a7.36 7.36 0 0 1-2.14 5.39h-.01l-.11.1a3.39 3.39 0 0 0-1.14 2.42 62.5 62.5 0 0 0-.13 4.62l6.78 6.78c.1-2.95.08-5.74-.05-7.84-.05-.79-.17-1.7-.3-2.43-.05-.23-.2-.44-.2-.63v-25.6c0-2.18 2.05-3.2 3.93 1.58 1.88 4.8 1.35 21.84 1.35 23.05 0 .26-.22.56-.59.86l-.4.29c-1.12.9-1.28 1.95-1.34 2.88-.14 2.26-.25 5.31-.28 8.51l9.34-9.34c-.02-1.1-.05-2.08-.1-2.9-.02-.32-.1-.64-.16-.98-.16-.95-1.35-1.74-2.03-2.43-1.33-1.33-2.19-3.58-2.19-6.11 0-4.11 2.28-9.32 5.1-9.32 2.8 0 5.08 5.2 5.08 9.32 0 2.53-.85 4.78-2.18 6.11-.69.7-1.87 1.48-2.04 2.43-.06.34-.14.66-.16.98l-.07 1.65 13.05-13.05L24.36 0 0 24.36 14.64 39c-.02-1.16-.06-2.2-.12-3.07z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M24 28.631a4.278 4.278 0 1 0 0-8.556 4.278 4.278 0 0 0 0 8.556z" fill="#61DAFB"/><path d="M24 33.118c12.678 0 22.956-3.924 22.956-8.765 0-4.84-10.278-8.765-22.956-8.765-12.679 0-22.957 3.924-22.957 8.765 0 4.841 10.278 8.765 22.957 8.765z" stroke="#61DAFB"/><path d="M16.409 28.736c6.34 10.98 14.877 17.918 19.07 15.498 4.191-2.42 2.451-13.284-3.888-24.264C25.25 8.99 16.714 2.053 12.52 4.473 8.33 6.892 10.07 17.756 16.41 28.736z" stroke="#61DAFB"/><path d="M16.409 19.97c-6.34 10.98-8.08 21.843-3.887 24.264 4.192 2.42 12.73-4.518 19.069-15.498 6.34-10.98 8.08-21.843 3.887-24.264-4.192-2.42-12.73 4.519-19.069 15.498z" stroke="#61DAFB"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(0 3)" d="M0 0H48V42.706H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 874 B

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M23.76 12.48h20.16v8.16H23.76v-8.16z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.52 39.12a4.794 4.794 0 0 1-4.152-2.4 4.773 4.773 0 0 0-.648 2.4 4.8 4.8 0 0 0 4.8 4.8h4.8v-4.8h-4.8z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M27.115 13.771L43.92 12.72v-2.4a4.8 4.8 0 0 0-4.8-4.8h-21.6l-.6-1.04a.693.693 0 0 0-1.2 0l-.6 1.04-.6-1.04a.693.693 0 0 0-1.2 0l-.6 1.04-.6-1.04a.693.693 0 0 0-1.2 0l-.6 1.04h-.017l-.994-.994a.694.694 0 0 0-1.159.312l-.326 1.224-1.248-.336a.694.694 0 0 0-.847.85l.333 1.248-1.224.326a.694.694 0 0 0-.312 1.16l.994.995v.015l-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6a4.8 4.8 0 0 0 4.8 4.8h28.8a4.8 4.8 0 0 0 4.8-4.8v-19.2l-16.805-1.051a2.553 2.553 0 0 1 0-5.098z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M34.32 43.92h7.2v-9.6h-7.2v9.6z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M46.32 37.92c-.053 0-.103.01-.154.014l-.028-.108a1.2 1.2 0 0 0-.461-2.306 1.19 1.19 0 0 0-.725.25 6.115 6.115 0 0 0-.08-.082 1.192 1.192 0 0 0-.027-1.477 1.2 1.2 0 0 0-2.034.289l-.105-.026c.004-.05.014-.101.014-.154a1.2 1.2 0 0 0-2.4 0c0 .053.01.103.014.154l-.105.026a1.2 1.2 0 0 0-2.304.47c0 .272.09.516.242.718a4.8 4.8 0 1 0 8 4.617c.05.005.1.015.153.015a1.2 1.2 0 0 0 0-2.4" fill="#44D860"/><path fill-rule="evenodd" clip-rule="evenodd" d="M36.72 29.52h7.2v-4.8h-7.2v4.8z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M46.32 27.72a.6.6 0 0 0 0-1.2l-.077.007-.014-.053a.6.6 0 0 0-.23-1.154.584.584 0 0 0-.36.125 1.388 1.388 0 0 0-.044-.041.587.587 0 0 0-.014-.738.6.6 0 0 0-1.015.145 2.397 2.397 0 0 0-2.461.728 2.4 2.4 0 0 0 2.46 3.89.6.6 0 1 0 1.033-.593l.038-.04c.1.076.226.124.362.124a.6.6 0 0 0 .23-1.152l.015-.055c.024.002.05.007.077.007z" fill="#44D860"/><path fill-rule="evenodd" clip-rule="evenodd" d="M15.12 13.32a.6.6 0 0 1-.6-.6 1.8 1.8 0 0 0-3.6 0 .6.6 0 1 1-1.2 0 3 3 0 1 1 6 0 .6.6 0 0 1-.6.6" fill="#000"/><path fill-rule="evenodd" clip-rule="evenodd" d="M24.72 43.92h14.4a4.8 4.8 0 0 0 4.8-4.8v-16.8h-14.4a4.8 4.8 0 0 0-4.8 4.8v16.8z" fill="#FFFF50"/><path fill-rule="evenodd" clip-rule="evenodd" d="M40.325 29.76h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0 4.8h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0 4.8h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0-11.955h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0 4.755h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0 4.8h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm3.595-22.174h-.007c-.744.024-1.095.768-1.404 1.42-.322.685-.572 1.129-.982 1.117-.454-.017-.713-.528-.986-1.07-.317-.624-.677-1.33-1.43-1.304-.733.024-1.086.672-1.395 1.241-.331.605-.557.974-.989.96-.463-.02-.708-.434-.994-.917-.316-.537-.684-1.14-1.423-1.12-.72.024-1.073.573-1.385 1.056-.328.51-.559.823-.998.803-.475-.016-.72-.36-1-.76-.32-.451-.678-.96-1.417-.936-.703.024-1.056.475-1.368.87-.295.375-.525.673-1.013.654a.24.24 0 0 0-.019.48c.725.024 1.092-.432 1.411-.838.281-.36.526-.667 1.006-.684.463-.02.68.267 1.008.73.312.441.662.94 1.375.965.744.024 1.104-.53 1.419-1.023.285-.441.528-.82.996-.838.432-.014.664.324.996.884.312.528.662 1.125 1.39 1.152.748.024 1.11-.63 1.425-1.21.273-.5.533-.972.989-.989.432-.01.655.384.986 1.04.312.611.662 1.305 1.4 1.331h.04c.734 0 1.085-.736 1.392-1.39.274-.575.533-1.122.977-1.144v-.48z" fill="#000"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.92 43.92h9.6v-9.6h-9.6v9.6z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M34.32 37.92c-.053 0-.103.01-.154.014l-.028-.108a1.2 1.2 0 0 0-.461-2.306 1.19 1.19 0 0 0-.725.25 6.115 6.115 0 0 0-.08-.082 1.192 1.192 0 0 0-.027-1.477 1.2 1.2 0 0 0-2.034.289l-.105-.026c.004-.05.014-.101.014-.154a1.2 1.2 0 0 0-2.4 0c0 .053.01.103.014.154l-.105.026a1.2 1.2 0 0 0-2.304.47c0 .272.09.516.242.718a4.8 4.8 0 1 0 8 4.617c.05.005.1.015.153.015a1.2 1.2 0 0 0 0-2.4" fill="#44D860"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.92 29.52h9.6v-4.8h-9.6v4.8z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M31.92 27.72a.6.6 0 0 0 0-1.2l-.077.007-.014-.053a.6.6 0 0 0-.23-1.154.584.584 0 0 0-.36.125 1.388 1.388 0 0 0-.044-.041.587.587 0 0 0-.014-.738.6.6 0 0 0-1.015.145 2.397 2.397 0 0 0-2.461.728 2.4 2.4 0 0 0 2.46 3.89.6.6 0 1 0 1.033-.593l.038-.04c.1.076.226.124.362.124a.6.6 0 0 0 .23-1.152l.015-.055c.024.002.05.007.077.007z" fill="#44D860"/><path fill-rule="evenodd" clip-rule="evenodd" d="M34.32 10.02a.652.652 0 0 1-.23-.046.623.623 0 0 1-.195-.13.63.63 0 0 Line truncated

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M23.76 12.48h20.16v8.16H23.76v-8.16z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.52 39.12a4.794 4.794 0 0 1-4.152-2.4 4.773 4.773 0 0 0-.648 2.4 4.8 4.8 0 0 0 4.8 4.8h4.8v-4.8h-4.8z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M27.115 13.771L43.92 12.72v-2.4a4.8 4.8 0 0 0-4.8-4.8h-21.6l-.6-1.04a.693.693 0 0 0-1.2 0l-.6 1.04-.6-1.04a.693.693 0 0 0-1.2 0l-.6 1.04-.6-1.04a.693.693 0 0 0-1.2 0l-.6 1.04h-.017l-.994-.994a.694.694 0 0 0-1.159.312l-.326 1.224-1.248-.336a.694.694 0 0 0-.847.85l.333 1.248-1.224.326a.694.694 0 0 0-.312 1.16l.994.995v.015l-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6-1.04.6a.694.694 0 0 0 0 1.2l1.04.6a4.8 4.8 0 0 0 4.8 4.8h28.8a4.8 4.8 0 0 0 4.8-4.8v-19.2l-16.805-1.051a2.553 2.553 0 0 1 0-5.098z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M34.32 43.92h7.2v-9.6h-7.2v9.6z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M46.32 37.92c-.053 0-.103.01-.154.014l-.028-.108a1.2 1.2 0 0 0-.461-2.306 1.19 1.19 0 0 0-.725.25 6.115 6.115 0 0 0-.08-.082 1.192 1.192 0 0 0-.027-1.477 1.2 1.2 0 0 0-2.034.289l-.105-.026c.004-.05.014-.101.014-.154a1.2 1.2 0 0 0-2.4 0c0 .053.01.103.014.154l-.105.026a1.2 1.2 0 0 0-2.304.47c0 .272.09.516.242.718a4.8 4.8 0 1 0 8 4.617c.05.005.1.015.153.015a1.2 1.2 0 0 0 0-2.4" fill="#44D860"/><path fill-rule="evenodd" clip-rule="evenodd" d="M36.72 29.52h7.2v-4.8h-7.2v4.8z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M46.32 27.72a.6.6 0 0 0 0-1.2l-.077.007-.014-.053a.6.6 0 0 0-.23-1.154.584.584 0 0 0-.36.125 1.388 1.388 0 0 0-.044-.041.587.587 0 0 0-.014-.738.6.6 0 0 0-1.015.145 2.397 2.397 0 0 0-2.461.728 2.4 2.4 0 0 0 2.46 3.89.6.6 0 1 0 1.033-.593l.038-.04c.1.076.226.124.362.124a.6.6 0 0 0 .23-1.152l.015-.055c.024.002.05.007.077.007z" fill="#44D860"/><path fill-rule="evenodd" clip-rule="evenodd" d="M15.12 13.32a.6.6 0 0 1-.6-.6 1.8 1.8 0 0 0-3.6 0 .6.6 0 1 1-1.2 0 3 3 0 1 1 6 0 .6.6 0 0 1-.6.6" fill="#000"/><path fill-rule="evenodd" clip-rule="evenodd" d="M24.72 43.92h14.4a4.8 4.8 0 0 0 4.8-4.8v-16.8h-14.4a4.8 4.8 0 0 0-4.8 4.8v16.8z" fill="#FFFF50"/><path fill-rule="evenodd" clip-rule="evenodd" d="M40.325 29.76h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0 4.8h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0 4.8h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0-11.955h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0 4.755h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm0 4.8h-12.01a.24.24 0 0 1 0-.48h12.01a.24.24 0 1 1 0 .48zm3.595-22.174h-.007c-.744.024-1.095.768-1.404 1.42-.322.685-.572 1.129-.982 1.117-.454-.017-.713-.528-.986-1.07-.317-.624-.677-1.33-1.43-1.304-.733.024-1.086.672-1.395 1.241-.331.605-.557.974-.989.96-.463-.02-.708-.434-.994-.917-.316-.537-.684-1.14-1.423-1.12-.72.024-1.073.573-1.385 1.056-.328.51-.559.823-.998.803-.475-.016-.72-.36-1-.76-.32-.451-.678-.96-1.417-.936-.703.024-1.056.475-1.368.87-.295.375-.525.673-1.013.654a.24.24 0 0 0-.019.48c.725.024 1.092-.432 1.411-.838.281-.36.526-.667 1.006-.684.463-.02.68.267 1.008.73.312.441.662.94 1.375.965.744.024 1.104-.53 1.419-1.023.285-.441.528-.82.996-.838.432-.014.664.324.996.884.312.528.662 1.125 1.39 1.152.748.024 1.11-.63 1.425-1.21.273-.5.533-.972.989-.989.432-.01.655.384.986 1.04.312.611.662 1.305 1.4 1.331h.04c.734 0 1.085-.736 1.392-1.39.274-.575.533-1.122.977-1.144v-.48z" fill="#000"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.92 43.92h9.6v-9.6h-9.6v9.6z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M34.32 37.92c-.053 0-.103.01-.154.014l-.028-.108a1.2 1.2 0 0 0-.461-2.306 1.19 1.19 0 0 0-.725.25 6.115 6.115 0 0 0-.08-.082 1.192 1.192 0 0 0-.027-1.477 1.2 1.2 0 0 0-2.034.289l-.105-.026c.004-.05.014-.101.014-.154a1.2 1.2 0 0 0-2.4 0c0 .053.01.103.014.154l-.105.026a1.2 1.2 0 0 0-2.304.47c0 .272.09.516.242.718a4.8 4.8 0 1 0 8 4.617c.05.005.1.015.153.015a1.2 1.2 0 0 0 0-2.4" fill="#44D860"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.92 29.52h9.6v-4.8h-9.6v4.8z" fill="#3ECC5F"/><path fill-rule="evenodd" clip-rule="evenodd" d="M31.92 27.72a.6.6 0 0 0 0-1.2l-.077.007-.014-.053a.6.6 0 0 0-.23-1.154.584.584 0 0 0-.36.125 1.388 1.388 0 0 0-.044-.041.587.587 0 0 0-.014-.738.6.6 0 0 0-1.015.145 2.397 2.397 0 0 0-2.461.728 2.4 2.4 0 0 0 2.46 3.89.6.6 0 1 0 1.033-.593l.038-.04c.1.076.226.124.362.124a.6.6 0 0 0 .23-1.152l.015-.055c.024.002.05.007.077.007z" fill="#44D860"/><path fill-rule="evenodd" clip-rule="evenodd" d="M34.32 10.02a.652.652 0 0 1-.23-.046.623.623 0 0 1-.195-.13.63.63 0 0 Line truncated

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -0,0 +1 @@
<svg fill="none" height="224" viewBox="0 0 313 224" width="313" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="82.2917" x2="82.2917" y1="173.09" y2="148.457"><stop offset="0" stop-color="#fffa8f"/><stop offset="1" stop-color="#fb784b"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="265.863" x2="274.954" y1="65.0136" y2="57.5404"><stop offset="0" stop-color="#5bcb99"/><stop offset="1" stop-color="#85a8e8"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="93.8362" x2="377.08" y1="155.311" y2="8.34586"><stop offset="0" stop-color="#34e28b"/><stop offset="1"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="145.861" x2="168.138" y1="183.775" y2="183.775"><stop offset="0" stop-color="#c297ff"/><stop offset="1" stop-color="#ae31bb"/></linearGradient><linearGradient id="e" gradientUnits="userSpaceOnUse" x1="55.0887" x2="336.714" y1="291.09" y2="71.4394"><stop offset="0"/><stop offset="1" stop-color="#d23de2"/></linearGradient><linearGradient id="f" gradientUnits="userSpaceOnUse" x1="263.41" x2="252.01" y1="52.6137" y2="91.0983"><stop offset="0"/><stop offset="1" stop-opacity="0"/></linearGradient><linearGradient id="g" gradientUnits="userSpaceOnUse" x1="159.166" x2="239.253" y1="201.53" y2="220.927"><stop offset="0" stop-color="#2db5f9"/><stop offset="1"/></linearGradient><linearGradient id="h" gradientUnits="userSpaceOnUse" x1="96.5719" x2="43.6645" y1="-.126831" y2="145.22"><stop offset="0"/><stop offset=".21" stop-color="#48080a"/><stop offset=".42" stop-color="#891014"/><stop offset=".61" stop-color="#bc151b"/><stop offset=".78" stop-color="#e01a21"/><stop offset=".91" stop-color="#f71c24"/><stop offset="1" stop-color="#ff1d25"/></linearGradient><path d="m38.2617 144.03.7378 1.166c3.8096 5.405 8.2521 10.335 13.2328 14.685 11.5851 9.488 26.1042 14.662 41.0788 14.637h2.8322c10.3997-.846 20.5917-3.378 30.1787-7.497-4.3.69-8.642 1.088-12.995 1.19-7.401-.006-14.7172-1.568-21.4745-4.586s-12.8042-7.424-17.748-12.931c-12.2646.101-24.4339-2.162-35.8428-6.664z" fill="url(#a)"/><path d="m93.2884 44.6647c11.1826-.0097 22.1776 2.8711 31.9196 8.363 9.741 5.492 17.897 13.4085 23.677 22.9816-6.702-6.0787-14.851-10.3363-23.668-12.3656-8.817-2.0294-18.008-1.7625-26.6926.775 11.8996 5.7358 28.1076 10.115 50.4326 11.6382 0 0-1.095-21.2534-28.299-32.368-38.0796-15.4938-43.3394-12.4236-52.9784-28.56-1.4233 11.2752 1.6742 22.6555 8.6156 31.654 5.5461-1.4527 11.2601-2.165 16.9932-2.1182z" fill="#f15a24"/><path d="m120.659 43.6885c-13.019-5.1884-22.1816-8.4252-29.036-10.9004 1.7712 4.36 4.1772 8.4342 7.14 12.0904 10.237.845 20.127 4.1075 28.857 9.5194 8.73 5.4118 16.051 12.8185 21.361 21.611-8.147-7.4073-18.409-12.0765-29.345-13.3518 9.045 5.3966 18.614 9.8621 28.56 13.328h.833s-1.143-21.182-28.37-32.2966z" fill="#ed1c24"/><path d="m148.909 76.0568h.143c.428-2.38 2.975-20.6822-17.731-36.7472-30.892-23.9666-36.9138-26.2038-38.3894-38.88919-2.4787 5.00087-3.8762 10.46769-4.1018 16.04459-.2255 5.5769.7261 11.1387 2.7928 16.3234 6.8306 2.38 16.0174 5.593 29.0124 10.9004 27.251 11.1146 28.322 32.368 28.322 32.368z" fill="#c1272d"/><path d="m242.658 55.1604c11.593-.0141 22.8 4.1613 31.558 11.7572 10.125.4387 19.762 4.4723 27.18 11.3764 0 0-25.966-37.7229-71.733-22.0388-4.101 1.4783-8.028 3.4017-11.71 5.7358 7.465-4.4705 16.003-6.8313 24.705-6.8306z" fill="url(#b)"/><path d="m242.658 55.1604c-8.7-.0091-17.238 2.3435-24.705 6.8068l1.333-.8092c-36.985 21.777-82.324 88.703-145.18 89.536 4.9544 5.485 11.0061 9.868 17.7628 12.865 6.7568 2.998 14.0682 4.542 21.4592 4.532 4.354-.102 8.695-.5 12.995-1.19 40.889-16.85 80.159-57.12 97.985-76.6594 16.517-18.1118 34.629-24.276 49.98-23.3478-8.78-7.6035-20.016-11.7714-31.63-11.7334z" fill="url(#c)"/><path d="m147.17 184.87c-.827 2.701-1.267 5.506-1.309 8.33-.006 3.27.491 6.521 1.476 9.639-.528-3.886-.087-7.842 1.283-11.517 1.371-3.675 3.628-6.954 6.571-9.546l.19-.714c2.086-6.858 6.628-12.705 12.757-16.422-1.936.623-3.835 1.354-5.688 2.19-3.642 1.806-6.876 4.339-9.504 7.441-2.627 3.102-4.593 6.709-5.776 10.599z" fill="url(#d)"/><path d="m312.676 113.589c.015-12.694-3.956-25.0732-11.353-35.3903-7.414-6.8521-17.021-10.8498-27.108-11.2812 5.241 4.5328 9.443 10.1431 12.318 16.4479s4.356 13.1557 4.342 20.0846c0 16.208-5.878 34.748-24.942 44.721-27.751 14.518-70.329 7.616-97.985 16.66-6.115 3.715-10.648 9.552-12.733 16.398l-.19.714c8.092-7.14 24.062-12.21 55.216-8.187 22.253 2.856 39.912 3.641 55.097-.714 14.679-3.203 27.578-11.897 36.057-24.3 7.324-10.26 11.267-22.547 11.281-35.153z" fill="url(#e)"/><path d="m312.676 113.589c.015-12.694-3.956-25.0732-11.353-35.3903-7.414-6.8521-17.021-10.8498-27.108-11.2812 5.241 4.5328 9.443 10.1431 12.318 16.4479s4.356 13.1557 4.342 20.0846c0 16.208-5.878 34.748-24.942 44.721-27.751 14.518-70.329 7.616-97.985 16.66-6.115 3.715-10.648 9.552-12.733 16.398l-.19.714c8.092-7.14 24.062-12.21 55.216-8.187 22.253 2.Line truncated

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)" fill="#222"><path d="M7.437 32.472c-.281 0-.422-.324-.422-.972V14.816c0-.145-.025-.252-.074-.32-.049-.067-.11-.091-.184-.072-1.054.213-1.587.32-1.599.32-.135.018-.239-.01-.312-.088-.062-.087-.092-.242-.092-.464v-1.117c0-.31.14-.489.422-.537l4.192-.87c.122-.02.202-.03.239-.03.245 0 .367.204.367.61V31.5c0 .648-.147.972-.44.972H7.436zm9.192 0c-.282 0-.423-.324-.423-.972V14.816c0-.145-.025-.252-.074-.32-.049-.067-.11-.091-.184-.072-1.053.213-1.587.32-1.599.32-.135.018-.239-.01-.312-.088-.062-.087-.092-.242-.092-.464v-1.117c0-.31.14-.489.423-.537l4.19-.87c.123-.02.203-.03.24-.03.245 0 .367.204.367.61V31.5c0 .648-.147.972-.44.972h-2.096zm12.242.029c-.453 0-.851-.03-1.194-.087a3.461 3.461 0 0 1-1.03-.392 2.518 2.518 0 0 1-.827-.783c-.209-.339-.38-.784-.515-1.335-.122-.561-.184-1.224-.184-1.988v-7.95c0-.174-.067-.261-.202-.261h-1.176c-.294 0-.441-.213-.441-.638v-.552c0-.425.146-.638.44-.638h1.177c.123 0 .19-.13.202-.392l.313-5.136c.012-.493.165-.74.46-.74h1.286c.147 0 .258.054.331.16.073.097.11.29.11.58v5.136c0 .261.074.392.22.392h2.372c.147 0 .258.043.331.13.074.087.11.257.11.508v.552c0 .251-.036.42-.11.507-.073.087-.184.13-.33.13h-2.372c-.086 0-.147.015-.184.044-.024.03-.037.102-.037.218v7.95c0 .813.086 1.442.258 1.886.183.445.386.726.606.842.233.106.527.16.883.16h1.268c.33 0 .496.145.496.435v.769c0 .329-.177.493-.533.493h-1.728zm7.206 2.031c.331 0 .638-.222.92-.667.293-.445.44-.987.44-1.625 0-.252-.122-.784-.367-1.596l-3.86-11.911a1.918 1.918 0 0 1-.037-.32c0-.357.092-.536.276-.536h1.746c.33 0 .545.242.643.725l2.776 9.924c.049.106.08.16.092.16.049 0 .073-.054.073-.16l2.28-9.88c.086-.513.288-.77.606-.77h1.25c.209 0 .313.194.313.581 0 .02-.019.13-.055.334l-3.309 13.478c-.343 1.499-.778 2.534-1.305 3.105-.515.57-1.201.87-2.059.9h-.239c-.98 0-1.618-.194-1.912-.581-.061-.087-.092-.261-.092-.523 0-.6.135-.9.405-.9.037 0 .245.044.625.131.38.088.643.131.79.131z"/><path d="M6.757 14.424l.183.562.025-.005.025-.006-.233-.551zm-1.599.32v-.581h-.066l-.066.01.132.57zm-.312-.088l-.643.282.024.034.03.032.589-.348zm.33-2.118l.157.567.015-.003.016-.003-.188-.561zm4.192-.87l-.145-.57-.021.004-.022.004.188.561zm-1.93 20.224c.13 0 .25.044.327.103.06.046.065.078.05.046-.03-.07-.065-.235-.065-.541H6.28c0 .341.034.663.145.917.107.248.396.635 1.012.635v-1.16zm.312-.392V14.816H6.28V31.5h1.47zm0-16.684c0-.173-.024-.405-.17-.607l-1.277.575a.242.242 0 0 1-.027-.046l-.003-.007.004.028a.596.596 0 0 1 .002.057h1.47zm-.17-.607a.87.87 0 0 0-.393-.3 1.075 1.075 0 0 0-.662-.036l.465 1.102a.758.758 0 0 1-.46-.028.501.501 0 0 1-.227-.163l1.277-.575zm-1.005-.347a572.4 572.4 0 0 1-1.487.298l-.07.013-.008.002.015-.003.027-.003s.047-.006.106-.006v1.16a.926.926 0 0 0 .198-.02l.085-.017.306-.06 1.193-.24-.365-1.124zm-1.549.31a.495.495 0 0 1 .198.017c.1.027.17.076.21.119l-1.177.696c.114.12.273.222.477.28.199.054.392.053.556.03l-.264-1.142zm.462.202a.23.23 0 0 1 .017.027l.005.01s-.003-.007-.007-.026a1.042 1.042 0 0 1-.014-.193h-1.47c0 .227.023.517.184.746l1.285-.564zm0-.182v-1.117h-1.47v1.117h1.47zm0-1.117c0-.044.006-.069.009-.08.003-.009.002 0-.013.02a.253.253 0 0 1-.079.063.256.256 0 0 1-.072.027l-.313-1.134c-.318.054-.6.197-.785.431a1.06 1.06 0 0 0-.217.673h1.47zm-.124.024l4.19-.87-.374-1.123-4.19.87.374 1.123zm4.148-.863l.119-.017.017-.002-.01.001h-.031v-1.16c-.124 0-.28.024-.384.04l.289 1.138zm.095-.017a.553.553 0 0 1-.261-.067.336.336 0 0 1-.128-.112c-.008-.014-.001-.008.007.032a.916.916 0 0 1 .014.176h1.47c0-.231-.03-.488-.163-.707a.943.943 0 0 0-.357-.34 1.206 1.206 0 0 0-.582-.143v1.16zm-.368.029V31.5h1.47V12.248H9.24zm0 19.252c0 .146-.008.264-.022.356a.705.705 0 0 1-.045.177c-.014.03-.008-.002.053-.047a.535.535 0 0 1 .308-.094v1.16c.264 0 .506-.078.694-.216.171-.126.27-.28.33-.411.116-.258.152-.582.152-.925H9.24zm.294.392H7.437v1.16h2.096v-1.16zm6.415-17.468l.183.562.025-.005.025-.006-.233-.551zm-1.599.32v-.581h-.066l-.065.01.131.57zm-.312-.088l-.643.282.024.034.03.032.589-.348zm.33-2.118l.157.567.015-.003.016-.003-.187-.561zm4.192-.87l-.144-.57-.022.004-.021.004.187.561zm-1.93 20.224c.13 0 .25.044.327.103.06.046.065.078.051.046-.03-.07-.066-.235-.066-.541h-1.47c0 .341.034.663.145.917.108.248.396.635 1.013.635v-1.16zm.312-.392V14.816h-1.47V31.5h1.47zm0-16.684c0-.173-.024-.405-.17-.607l-1.277.575a.237.237 0 0 1-.027-.046l-.003-.007c0 .004.003.013.004.028a.605.605 0 0 1 .002.057h1.471zm-.17-.607a.87.87 0 0 0-.393-.3 1.074 1.074 0 0 0-.662-.036l.465 1.102a.758.758 0 0 1-.46-.028.5.5 0 0 1-.227-.163l1.277-.575zm-1.005-.347a571.934 571.934 0 0 1-1.557.311l-.008.002.015-.003.027-.003s.047-.006.106-.006v1.16a.927.927 0 0 0 .165-.014l.034-.006.085-.017a607.434 607.434 0 0 0 1.498-.3l-.365-1.124zm-1.548.31a.495.495 0 0 1 .197.017c.1.027.17.076.21.119l-1.177.696c.114.12.274.222.477.28.199.054.392.053.556.03l-.263-1.142zm.462.202a.23.23 0 0 1 .02.037l-.006-.026a1.04 1.04 0 0 1-.014-.193h-1.47c0 .227.023.517.184.746l1.286-.564zm0-.182v-1.117h-1.47v1.117h1.47Line truncated

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.381 45.872l.147-.341.019-.049c.114-.27.228-.554.337-.836.144-.37.272-.743.416-1.106.08-.21.155-.422.234-.63.185-.5.438-1.168.745-1.812.182-.376.332-.657.487-.917.155-.26.359-.563.544-.83l.168-.227.237-.15a2.775 2.775 0 0 1 .446-.224c.166-.067.362-.135.593-.205.323-.095.68-.184 1.123-.27.63-.139 1.33-.271 2.2-.401a91.474 91.474 0 0 1 4.375-.57c2.967-.32 5.917-.542 8.77-.693 1.51-.07 2.665-.103 3.747-.103h.683c.768.003 1.535.052 2.298.149.24.033.478.088.71.165.07.024.13.051.168.067l.223.1.177.166a9.865 9.865 0 0 1 1.009 1.098 19.364 19.364 0 0 1 2.344 3.921 17.555 17.555 0 0 1 1.332 3.992l.337 1.761h-4.182l-.25-1.174c-.272-1.276-3.731-4.638-5.452-5.3-.174-.066-.917-.271-3.18-.271-4.663 0-11.282.825-12.236 1.022-1.115.517-2.845 2.726-3.826 4.9L10.72 48" fill="#fff"/><path d="M35.056 39.191a8.81 8.81 0 0 0-.677-.765l-.171-.163-.044-.016a2.443 2.443 0 0 0-.424-.095 16.91 16.91 0 0 0-2.096-.132 72.576 72.576 0 0 0-4.332.095c-2.896.14-5.795.373-8.68.687-1.442.156-2.88.335-4.305.56-.71.11-1.42.232-2.113.383a12.52 12.52 0 0 0-1.011.252c-.161.046-.316.1-.452.154-.06.024-.118.05-.174.081l-.027.017c-.171.23-.327.462-.476.706a8.71 8.71 0 0 0-.419.792 20.462 20.462 0 0 0-.696 1.693c-.215.579-.424 1.166-.655 1.75-.112.292-.229.495-.351.787-.057.138-.117.325-.177.325h1.955c1.058-2.435 3.043-4.944 4.71-5.62.46-.187 7.848-1.058 12.732-1.058 1.811 0 3.065.065 3.728.322 2.002.768 5.983 4.462 6.393 6.356h1.134a14.266 14.266 0 0 0-1.218-3.59 17.137 17.137 0 0 0-2.154-3.521z" fill="#BA8449"/><path d="M34.193 38.25l-.003-.003-.004-.003.01.01-.003-.003z" fill="#fff"/><path d="M34.193 38.25l-.003-.003-.004-.003.01.01-.003-.003z" fill="#BA8449"/><path d="M37.156 47.924l-.234-1.2a14.658 14.658 0 0 0-1.112-3.32 16.334 16.334 0 0 0-1.961-3.288 5.79 5.79 0 0 0-.419-.481 16.775 16.775 0 0 0-1.827-.103h-.62c-1.034 0-2.146.03-3.6.097a148.6 148.6 0 0 0-8.585.679c-1.632.181-2.978.354-4.234.54-.816.128-1.455.244-2.026.369-.367.078-.653.151-.9.222l-.096.03c-.054.086-.108.17-.163.27a9.135 9.135 0 0 0-.353.668c-.248.52-.46 1.082-.637 1.558-.079.21-.155.413-.228.62-.139.378-.272.767-.438 1.162-.117.298-.24.606-.367.903l-.016.044c-.055.13-.112.227-.169.36l-.397.87H6.093l-.384-.154h-.04a1.57 1.57 0 0 1-.89-1.623v-.067l.05-.206.065-.249c.046-.173.095-.338.144-.503A22.573 22.573 0 0 1 6.1 42.236c.302-.681.628-1.317.952-1.893a15.25 15.25 0 0 1 1.243-1.877c.148-.19.323-.358.519-.5a3.76 3.76 0 0 1 .345-.22c.159-.085.322-.16.49-.227.236-.094.478-.176.723-.246.462-.135.895-.23 1.224-.3.669-.138 1.36-.27 2.276-.397 1.324-.2 2.72-.374 4.44-.541 2.992-.306 5.983-.522 8.855-.644a82.239 82.239 0 0 1 3.445-.081h1.058c.834.004 1.667.061 2.493.173h.028c.301.045.598.118.886.216h.02a2.675 2.675 0 0 1 .658.34l.11.082.088.07.09.082c.087.08.174.162.255.246.313.317.607.65.881 1a20.551 20.551 0 0 1 2.467 4.185c.33.732.62 1.483.867 2.247.243.734.43 1.484.56 2.245l.286 1.723-4.204.005z" fill="#fff"/><path d="M7.847 47.924l1.028-2.123c.853-1.81 2.904-5.098 5.166-6.02h.044c.764-.285 8.43-1.164 13.093-1.164 2.624 0 3.604.27 4.112.458 2.366.906 6.472 4.743 7.027 7.018l.457 1.84-30.927-.009z" fill="#fff"/><path d="M35.636 47.924l-.272-1.122c-.272-1.069-3.495-4.285-5.167-4.925-.155-.06-.816-.244-3.016-.244-4.508 0-11.005.795-12.068 1-.876.433-2.447 2.254-3.483 4.453l-.42.838H7.38l.965-2.102c1.128-2.504 3.339-5.585 5.52-6.472.84-.343 8.606-1.206 13.308-1.206 2.05 0 3.41.16 4.278.492 2.357.906 6.784 4.869 7.342 7.498l.378 1.79h-3.535z" fill="#fff"/><path d="M37.387 47.924l-.438-.37c-1.656-1.531-2.11-4.68-1.594-5.986a1.704 1.704 0 0 1 2.075-1.1c.291-.02.99-.582 1.197-.92a1.832 1.832 0 0 1 1.683-.958c1.128 0 2.953.89 3.551 1.731.335.449.427 1.032.245 1.561-.016.043-.043.133-.087.254-.62 1.853-1.42 4.079-2.075 5.115l-.438.673h-4.12z" fill="#fff"/><path d="M37.248 47.924l1.42-2.272a23.661 23.661 0 0 0 1.479-2.723c-.802.557-1.93.93-3.595 1.003l-.046-.032-.03.154a1.869 1.869 0 0 1-1.685-1.116 1.85 1.85 0 0 1 .325-1.986 1.82 1.82 0 0 1 1.267-.595c1.632-.073 1.844-.457 2.151-1.036.416-.776 1.316-1.225 2.472-1.225.292-.002.583.029.868.092a2.98 2.98 0 0 1 2.447 2.834c.065 1.444-.723 3.516-2.344 6.199l-.443.703h-4.286z" fill="#fff"/><path d="M43.296 43.5c-1.857 0-3.878-1.204-4.914-2.391-.944-1.082-1.036-2.034-.949-2.645a1.77 1.77 0 0 1 1.857-1.51c.2.003.397.021.593.054.182-.811.617-2.523 2.377-2.523.08-.004.16-.004.24 0 .82.07 1.514.636 1.745 1.423.297-.157.629-.239.965-.238a2 2 0 0 1 1.463.649c.622.655.77 1.624.373 2.434.422.26.732.668.867 1.144.192.65.063 1.352-.348 1.893-.816 1.106-2.657 1.647-4.136 1.696l-.133.014zm-1.85-37.587c-1.68 1.047-3.515 3.448-5.75 6.735a1.86 1.86 0 0 1-1.575.81c-.83 0-1.87-.494-3.09-1.47-1.933-1.547-1.973-2.435-1.99-2.751a1.707 1.707 0 0 1 1.588-1.785c1.858-.173 3.397-1.28 4.895-2.348 1.498-1.068 2.877-2.063 4.525-2.063.33-.001.658.04.976.124" fill="#fff"/><path d="M30.782 8.958c4.492-.416 7.119-5.046 9.857-4.327-1.827 1.136-3.655 3.419-6.203 7.167-.6.88-4.878-2.726-3.654-2.8Line truncated

After

Width:  |  Height:  |  Size: 39 KiB

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M42.857 24h-12v3.428h8.229c-1.2 5.143-4.972 9.43-9.943 11.143L9.429 18.857c2.057-6 7.885-10.286 14.571-10.286 5.143 0 9.771 2.572 12.686 6.515l2.571-2.229C35.83 8.23 30.343 5.143 24 5.143c-8.914 0-16.457 6.343-18.343 14.743l22.629 22.628c8.228-2.057 14.571-9.6 14.571-18.514zm-37.714.171c0 4.8 1.886 9.429 5.486 13.029 3.6 3.6 8.4 5.486 13.028 5.486L5.143 24.17z" fill="#fff"/><path d="M24 0C10.8 0 0 10.8 0 24s10.8 24 24 24 24-10.8 24-24S37.2 0 24 0zM10.629 37.371c-3.6-3.6-5.486-8.4-5.486-13.028l18.686 18.514c-4.8-.171-9.6-1.886-13.2-5.486zm17.485 4.972L5.657 19.886C7.543 11.486 15.086 5.143 24 5.143c6.343 0 11.829 3.086 15.257 7.714l-2.571 2.229C33.77 11.143 29.143 8.57 24 8.57c-6.686 0-12.343 4.286-14.571 10.286l19.714 19.714c4.971-1.714 8.743-6 9.943-11.142h-8.229V24h12c0 8.914-6.343 16.457-14.743 18.343z" fill="#639"/></svg>

After

Width:  |  Height:  |  Size: 921 B

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M43.395 19.862c2.308-.11 4.35 1.68 4.55 3.99.863 11.174-9.364 23.692-23.775 24.092C12.025 48.244.013 38.521.013 23.82c0-2.317 1.915-4.195 4.225-4.195a4.19 4.19 0 0 1 4.184 4.195c0 9.67 7.776 15.928 15.542 15.737 9.56-.266 15.776-8.544 15.453-15.303a4.19 4.19 0 0 1 3.978-4.392z" fill="url(#paint0_linear)"/><path d="M32.944 24.145c0-2.372 1.935-4.295 4.321-4.295h6.298c2.387 0 4.382 1.923 4.382 4.295s-1.995 4.295-4.382 4.295h-6.298c-2.386 0-4.321-1.923-4.321-4.295zm-13.215.006a4.297 4.297 0 0 1 4.291-4.301 4.297 4.297 0 0 1 4.292 4.301 4.297 4.297 0 0 1-4.292 4.301 4.297 4.297 0 0 1-4.291-4.301z" fill="#00A672"/><path fill-rule="evenodd" clip-rule="evenodd" d="M28.193 4.055a4.19 4.19 0 0 1-4.006 4.365c-9.966.415-16.05 8.248-15.753 15.685.092 2.315-1.668 4.267-3.976 4.36-2.309.092-4.34-1.777-4.431-4.092C-.456 12.253 9.635.273 23.84.037a4.188 4.188 0 0 1 4.353 4.018z" fill="#00A672"/><defs><linearGradient id="paint0_linear" x1="24.005" y1="19.625" x2="24.005" y2="47.951" gradientUnits="userSpaceOnUse"><stop stop-color="#00583E"/><stop offset="1" stop-color="#00835C"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M23.829.005l-20.735 12 .002 23.964L23.878 47.92l20.736-12V11.97L23.829.004z" fill="#0E83CD"/><path d="M14.101 34.338l.002-10.362.002-10.363 1.882-1.101 1.804 1.067.019 4.235.02 4.234h12.05l.019-4.231.02-4.232 1.83-1.059 1.857 1.087V23.97l-.002 10.355-1.837 1.027-1.849-1.04-.02-4.21-.019-4.212H17.83l-.019 4.21.006 4.23-1.848 1.085-1.866-1.076z" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3)" d="M0 0H41.645V48H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 591 B

@@ -0,0 +1,5 @@
<svg width="43" height="48" viewBox="0 0 43 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.99 30.79V15.66C40.9899 14.6946 40.7381 13.746 40.2593 12.9077C39.7806 12.0694 39.0914 11.3705 38.26 10.88L24.26 2.65002C23.2394 2.04939 22.0743 1.73851 20.8902 1.75084C19.7061 1.76318 18.5478 2.09826 17.54 2.72002L5.09996 10.4C4.06029 11.0427 3.20217 11.9405 2.6071 13.0081C2.01203 14.0757 1.69976 15.2777 1.69995 16.5V31.98C1.6987 33.1936 2.01568 34.3863 2.61929 35.4391C3.2229 36.4919 4.09202 37.368 5.13996 37.98L18.09 45.55C18.9373 46.0468 19.8998 46.3137 20.882 46.3242C21.8642 46.3347 22.8321 46.0885 23.69 45.61L38.03 37.61C38.9267 37.1088 39.6737 36.3776 40.194 35.4917C40.7142 34.6059 40.989 33.5974 40.99 32.57V30.8V30.79Z" fill="#FF4088"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.97 0.0800684H21.35L21.38 0.0900684H21.51L21.55 0.100068H21.65L21.68 0.110068H21.78L21.82 0.120068H21.89L21.92 0.130068H21.99L22.02 0.140068H22.05L22.09 0.150068H22.12L22.16 0.160068H22.19L22.22 0.170068H22.26L22.29 0.180068H22.32L22.36 0.190068H22.39L22.42 0.200068H22.46L22.49 0.220068H22.56L22.59 0.230068L22.62 0.240068H22.66L22.69 0.260068H22.72L22.76 0.270068H22.79L22.82 0.290068H22.86L22.89 0.300068L22.92 0.310068H22.96V0.320068H22.99L23.02 0.340068H23.05L23.09 0.350068L23.12 0.360068L23.16 0.370068L23.19 0.380068L23.22 0.390068L23.26 0.400068L23.29 0.410068L23.32 0.420068L23.36 0.430068L23.39 0.440068L23.42 0.450068L23.45 0.460068L23.49 0.470068L23.52 0.480068L23.55 0.490068L23.59 0.500068L23.62 0.520068H23.65L23.68 0.540068H23.71V0.550068L23.75 0.560068L23.78 0.570068L23.81 0.590068H23.85L23.88 0.610068L23.91 0.620068L23.94 0.640068H23.97L24.01 0.660068L24.04 0.670068L24.07 0.690068L24.11 0.700068L24.14 0.720068H24.17L24.2 0.740068L24.23 0.760068H24.26V0.770068L24.3 0.780068L24.33 0.800068L24.36 0.810068L24.39 0.830068L24.42 0.840068L24.45 0.860068L24.48 0.870068L24.52 0.890068L24.55 0.900068L24.58 0.920068L24.61 0.930068L24.64 0.950068L24.67 0.960068L24.7 0.980068L24.74 1.00007L24.77 1.01007L24.8 1.03007L24.83 1.04007L24.86 1.06007L24.89 1.08007L24.92 1.10007L24.95 1.11007L24.98 1.13007L25.01 1.15007L25.04 1.17007L25.07 1.19007L39.09 9.42007L39.11 9.44007L39.14 9.45007L39.17 9.47007L39.19 9.48007L39.22 9.50007L39.24 9.51007L39.27 9.53007L39.29 9.54007L39.32 9.56007L39.34 9.58007L39.37 9.59007L39.39 9.61007L39.42 9.63007L39.44 9.64007L39.47 9.66007L39.49 9.67007L39.51 9.69007L39.54 9.71007L39.56 9.72007L39.58 9.74007L39.61 9.76007L39.63 9.77007L39.66 9.79007L39.68 9.81007L39.7 9.82007L39.73 9.84007L39.75 9.86007L39.77 9.87007L39.8 9.89007L39.82 9.91007L39.85 9.93007L39.87 9.95007L39.89 9.97007L39.92 9.99007L39.96 10.0201L40.01 10.0601L40.05 10.1001L40.1 10.1301L40.14 10.1701L40.19 10.2101L40.23 10.2501L40.27 10.2901L40.32 10.3301L40.36 10.3601L40.4 10.4001L40.44 10.4401L40.49 10.4801L40.53 10.5301L40.57 10.5701L40.61 10.6101L40.65 10.6501L40.69 10.6901L40.73 10.7401L40.77 10.7801L40.81 10.8201L40.84 10.8701L40.88 10.9101L40.92 10.9501L40.96 11.0001L41 11.0401L41.03 11.0901L41.07 11.1301L41.11 11.1801L41.14 11.2201L41.17 11.2701L41.21 11.3101V11.3201L41.24 11.3601L41.28 11.4101L41.31 11.4601L41.35 11.5001L41.38 11.5501L41.41 11.6001L41.44 11.6501L41.47 11.7001L41.51 11.7401L41.54 11.7901L41.57 11.8401L41.6 11.8901L41.63 11.9401L41.66 11.9901L41.69 12.0401L41.72 12.0901L41.75 12.1401L41.77 12.1901L41.8 12.2401L41.83 12.2901L41.86 12.3501L41.88 12.4001L41.91 12.4501L41.93 12.5001L41.96 12.5501L41.98 12.6101L42.01 12.6601L42.03 12.7101L42.05 12.7701L42.08 12.8201L42.1 12.8701L42.12 12.9301L42.14 12.9801L42.16 13.0301V13.0401L42.18 13.0901L42.2 13.1401L42.22 13.2001L42.24 13.2501L42.26 13.3101L42.28 13.3601L42.3 13.4201L42.31 13.4701L42.33 13.5301L42.35 13.5801L42.36 13.6401L42.38 13.6901V13.7001L42.4 13.7501L42.41 13.8101L42.43 13.8601V13.9201H42.44L42.45 13.9801L42.47 14.0301V14.0401L42.48 14.0901L42.49 14.1501L42.5 14.2101L42.51 14.2601L42.53 14.3201V14.3801L42.55 14.4301V14.5001L42.56 14.5501V14.5601L42.57 14.6101V14.6701L42.58 14.7001V14.7301L42.59 14.7601V14.8201L42.6 14.8501V14.9101L42.61 14.9401V15.0001L42.62 15.0301V15.1501L42.63 15.1801V33.0801L42.62 33.1201V33.2101L42.61 33.2401V33.3001L42.6 33.3301V33.3901L42.59 33.4201V33.4901L42.58 33.5201V33.5501H42.57V33.6101L42.56 33.6401V33.6701L42.55 33.7001V33.7301L42.54 33.7601V33.7901L42.53 33.8201V33.8501L42.52 33.8801V33.9101L42.51 33.9401V33.9701L42.5 34.0001L42.49 34.0301V34.0601L42.48 34.0901V34.1201L42.47 34.1501L42.46 34.1801V34.2101L42.45 34.2401L42.44 34.2701V34.3001L42.43 34.3301L42.42 34.3601L42.41 34.3901V34.4201L42.39 34.4501V34.4801L42.38 34.5101L42.37 34.5401V34.5701L42.35 34.6001V34.6301L42.34 34.6501L42.33 34.6801L42.32 34.7101L42.31 34.7401L42.3 34.7701L42.29 34.8001L42.28 34.8301L42.27 34.8601L42.26 34.8901L42.25 34.9201L42.24 34.9501L42.23 34.9801L42.22 35.0101L42.21 35.0401L42.2 35.0701L42.19 35.0901L42.18 35.1201L42.17 35.1501L42.16 35.1801L42.15 35.2101L42.14 35.2401L42.13 35.2701L42.12 35.3001L42.1 35.3201V35.3501L42.08 35.3801L42.07 35.4101L42.06 35.4401L42.05 35.4701L42.03 35.4901V35.5201H42.02L42.01 35.5501L42 35.5801L41.98 35.6101V35.6301H41.97L41.96 35.6601L41.95 35.6901L41.93 35.7201L41.92 35.7401L41.91 35.7701L41.89 35.8001V35.8201H41.88L41.87 35.8501L41.85 35.8801L41.84 35.9101L41.83 35.9301L41.81 35.9601L41.8 35.9901L41.79 36.0201L41.77 36.0401L41.76 36.0701L41.74 36.1001L41.73 36.1301L41.71 36.1501L41.7 36.1801L41.68 36.2001L41.67 36.2301L41.65 36.2601L41.64 36.2801L41.62 36.3101L41.61 36.3401L41.59 36.3601L41.58 36.3901L41.56 36.4201L41.55 36.4401L41.53 36.4701L41.51 36.4901L41.5 36.5201L41.48 36.5401L41.46 36.5701L41.45 36.6001L41.43 36.6201L41.41 36.6501L41.4 36.6701L41.38 36.7001L41.36 36.7201L41.34 36.7501L41.33 36.7701L41.31 36.7901L41.29 36.8201L41.28 36.840Line truncated
<path d="M11.53 36.7301V11.1001H16.89V20.4301H25.79V11.1001H31.17V36.7301H25.79V25.5301H16.89V36.7301H11.53Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,12 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="155 29 82 82">
<path d="m193.98 109.45-37.817-20.092 12.934-6.8171 14.561 7.7326 12.219-6.4411-14.562-7.7325 12.934-6.8334 37.817 20.092-12.934 6.817-13.757-7.3074-12.235 6.4573 13.774 7.3076-12.934 6.817z" fill="#000"/>
<path d="m193.98 69.482-37.817-20.092 12.933-6.817 14.562 7.7325 12.218-6.4411-14.562-7.7325 12.934-6.8334 37.817 20.092-12.933 6.817-13.757-7.3074-12.236 6.4574 13.774 7.3074-12.933 6.8171z" fill="url(#a)"/>
<defs>
<linearGradient id="a" x1="230.64" x2="174.89" y1="50.952" y2="80.993" gradientUnits="userSpaceOnUse">
<stop stop-color="#430470" offset=".0022437"/>
<stop stop-color="#8E01F0" offset=".38547"/>
<stop stop-color="#354CF6" offset=".63547"/>
<stop stop-color="#01FFFF" offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 833 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
<rect width="48" height="48" fill="#4180FC" rx="24"/>
<path fill="#fff" fill-rule="evenodd" d="M38.4327 17.0558l.1306.3047C39.5211 19.4721 40 21.6925 40 24c0 8.8163-7.1837 16-16 16S8 32.8163 8 24 15.1837 8 24 8c2.5905 0 5.0503.6095 7.3143 1.7415l.3047.1524-.2612.2177c-.653.5224-1.1537 1.1755-1.4802 1.9374l-.0871.2177-.1959-.0871C27.8313 11.3524 25.9592 10.917 24 10.917c-7.2272 0-13.083 5.8558-13.083 13.083S16.7728 37.083 24 37.083 37.083 31.2054 37.083 24c0-1.7197-.3265-3.4177-1.0014-5.0068l-.087-.2177.2176-.0871c.7619-.283 1.4585-.7619 2.0028-1.3714l.2177-.2612zm-3.962.3048c1.8395 0 3.3307-1.4912 3.3307-3.3306 0-1.8395-1.4912-3.3307-3.3307-3.3307-1.8394 0-3.3306 1.4912-3.3306 3.3307 0 1.8394 1.4912 3.3306 3.3306 3.3306zm-10.4708-.6531c-4.0272 0-7.2925 3.2653-7.2925 7.2926 0 4.0272 3.2653 7.2925 7.2925 7.2925 4.0272 0 7.2925-3.2653 7.2925-7.2925 0-4.0273-3.2653-7.2926-7.2925-7.2926z" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 1003 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
<rect width="48" height="48" fill="#4180FC" rx="24"/>
<path fill="#fff" fill-rule="evenodd" d="M38.4327 17.0558l.1306.3047C39.5211 19.4721 40 21.6925 40 24c0 8.8163-7.1837 16-16 16S8 32.8163 8 24 15.1837 8 24 8c2.5905 0 5.0503.6095 7.3143 1.7415l.3047.1524-.2612.2177c-.653.5224-1.1537 1.1755-1.4802 1.9374l-.0871.2177-.1959-.0871C27.8313 11.3524 25.9592 10.917 24 10.917c-7.2272 0-13.083 5.8558-13.083 13.083S16.7728 37.083 24 37.083 37.083 31.2054 37.083 24c0-1.7197-.3265-3.4177-1.0014-5.0068l-.087-.2177.2176-.0871c.7619-.283 1.4585-.7619 2.0028-1.3714l.2177-.2612zm-3.962.3048c1.8395 0 3.3307-1.4912 3.3307-3.3306 0-1.8395-1.4912-3.3307-3.3307-3.3307-1.8394 0-3.3306 1.4912-3.3306 3.3307 0 1.8394 1.4912 3.3306 3.3306 3.3306zm-10.4708-.6531c-4.0272 0-7.2925 3.2653-7.2925 7.2926 0 4.0272 3.2653 7.2925 7.2925 7.2925 4.0272 0 7.2925-3.2653 7.2925-7.2925 0-4.0273-3.2653-7.2926-7.2925-7.2926z" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 1003 B

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
<path fill="#333" d="M0 24a24 24 0 1 0 48 0 24 24 0 0 0-48 0z"/>
<path fill="#000" d="M25.22 5.55a.52.52 0 0 0 0 .22s.23 1.56-.03 2.25l-11.35 29.5a4.22 4.22 0 0 0 7.88 3.03l11.35-29.5c.22-.57 1.48-1.7 1.48-1.7.07-.04.12-.1.15-.16v-.01l.02-.04c.3-.76-1.6-2.2-4.21-3.2-2.62-1-4.98-1.2-5.27-.45l-.02.04v.02z" opacity=".3"/>
<path fill="url(#paint0_linear)" d="M24.9 5.3a.51.51 0 0 0-.01.22s.23 1.56-.03 2.25L13.5 37.27a4.22 4.22 0 0 0 7.88 3.03l11.35-29.5c.22-.58 1.49-1.7 1.49-1.7.06-.04.1-.1.14-.16v-.01l.03-.04c.29-.76-1.6-2.2-4.22-3.2-2.62-1-4.98-1.2-5.27-.45l-.01.04v.01z"/>
<path fill="url(#paint1_linear)" d="M28.45 18.64L20.27 39.9a3.06 3.06 0 0 1-3.91 1.72 3.12 3.12 0 0 1-1.83-3.93l5.23-13.59s.65-1.05 1.93-1.8c1.29-.75 2.34-.6 3.77-1.22 1.42-.64 3-2.45 3-2.45z"/>
<path fill="#333" d="M29.33 7.92c1.62.63 3.07.79 3.24.36.16-.43-1.02-1.28-2.65-1.9-1.62-.63-3.08-.8-3.24-.37-.17.43 1.02 1.28 2.65 1.91z"/>
<path fill="#fff" d="M24.89 5.52s.23 1.57-.03 2.25L13.5 37.27a4.22 4.22 0 0 0 3.58 5.68 4.22 4.22 0 0 1-1.41-4.85L26.8 9.19s-1.52-1.42-1.91-3.67z" opacity=".3"/>
<path fill="#fff" d="M21.24 24.87a.33.33 0 1 0 0-.65.33.33 0 0 0 0 .65zM23.4 27.42a.5.5 0 1 0 0-.99.5.5 0 0 0 0 1zM20.1 30.7a.93.93 0 1 0 0-1.87.93.93 0 0 0 0 1.86zM18.6 31.98a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM20.57 35.49a.33.33 0 1 0 0-.66.33.33 0 0 0 0 .66z" opacity=".5"/>
<defs>
<linearGradient id="paint0_linear" x1="18.32" x2="27.81" y1="22.36" y2="26.01" gradientUnits="userSpaceOnUse">
<stop stop-color="#919191"/>
<stop offset="1" stop-color="#fff"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="18.08" x2="23.83" y1="28.47" y2="30.68" gradientUnits="userSpaceOnUse">
<stop stop-color="#900"/>
<stop offset="1" stop-color="#E80000"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
<rect width="48" height="48" fill="#FBC547" rx="24"/>
<path fill="#000" fill-opacity=".45" d="M6 12.53L23.65 9v2.65L6 14.29v-1.76z"/>
<path fill="#fff" d="M23.65 9l17.64 3.53v1.76l-17.64-2.64V9z"/>
<path fill="#000" fill-opacity=".45" d="M6 15.88v16.24l3.97.44V22.4l3.97 7.33 4.15-7.33V33.8l4.67.62V13.6l-5.02.7-3.8 7.33-3.8-6.18-4.14.44z"/>
<path fill="#fff" d="M41.3 15.88v16.24l-3.98.44V22.4l-3.97 7.33-4.14-7.33V33.8l-4.68.62V13.6l5.03.7 3.8 7.33 3.79-6.18 4.14.44z"/>
<path fill="#000" fill-opacity=".45" d="M6 35.47L23.65 39v-2.65L6 33.71v1.76z"/>
<path fill="#fff" d="M23.65 39l17.64-3.53v-1.76l-17.64 2.64V39z"/>
</svg>

After

Width:  |  Height:  |  Size: 715 B

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="mask" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="48" height="48"><circle cx="24" cy="24" r="24" fill="#000"/></mask><g mask="url(#mask)"><circle cx="24" cy="24" r="23.2" fill="#000" stroke="#000" stroke-width="1.6"/><path d="M39.8687 42.0055L18.4378 14.4H14.3999V33.592H17.6302V18.5023L37.333 43.9587C38.222 43.3637 39.069 42.7108 39.8687 42.0055Z" fill="url(#gradient0)"/><rect x="30.6667" y="14.4" width="3.2" height="19.2" fill="url(#gradient1)"/></g><defs><linearGradient id="gradient0" x1="29.0666" y1="31.0667" x2="38.5332" y2="42.8" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><linearGradient id="gradient1" x1="32.2667" y1="14.4" x2="32.2132" y2="28.5001" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 997 B

@@ -0,0 +1,5 @@
<svg width="48" height="38" viewBox="0 0 48 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.7599 35.9199L14.6399 35.7999C14.3999 35.3199 14.3999 34.8399 14.3999 34.3599H2.99992L20.0399 4.11988L27.1199 16.9599L29.3999 15.2799L22.3199 2.43988C22.1999 2.19988 21.3599 0.879883 19.9199 0.879883C19.3199 0.879883 18.3599 1.11988 17.6399 2.43988L0.479919 33.0399C0.359919 33.2799 -0.360081 34.7199 0.359919 35.9199C0.599919 36.5199 1.31992 37.1199 2.87992 37.1199H17.2799C15.7199 37.1199 14.9999 36.5199 14.7599 35.9199Z" fill="#00C58E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.3999 33.1598L33.5999 8.31977C33.3599 8.07977 32.6399 6.75977 31.1999 6.75977C30.5999 6.75977 29.7599 6.99977 28.9199 8.31977L27.1199 11.1998V16.9598L31.1999 9.87977L44.8799 34.3598H39.7199C39.7913 34.853 39.7075 35.3563 39.4799 35.7998V35.9198C38.7599 37.1198 37.1999 37.1198 36.9599 37.1198H45.1199C45.3599 37.1198 46.9199 37.1198 47.6399 35.9198C47.8799 35.3198 48.1199 34.3598 47.3999 33.1598Z" fill="#108775"/>
<path d="M39.84 35.9199V35.7999L39.96 35.5599C40.08 35.1999 40.2 34.7199 40.08 34.3599L39.6 33.0399L28.8 14.08L27.24 11.2H27.12L25.56 14.08L14.64 33.0399L14.28 34.3599C14.1908 34.8922 14.275 35.4391 14.52 35.9199C14.88 36.5199 15.6 37.1199 17.04 37.1199H37.2C37.56 37.1199 39.12 37.1199 39.84 35.9199ZM27.12 16.96L37.08 34.3599H17.28L27.12 16.96Z" fill="#2F495E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,9 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.8476 7.89485L23.1513 0.380859H5.30562L0.609375 7.89485V27.6191H27.8476V7.89485Z" fill="#A98258"/>
<path d="M0.609375 7.89485H27.8476L23.1513 0.380859H5.30562L0.609375 7.89485Z" fill="#DAAE86"/>
<path d="M11.4111 24.8006V27.6183H17.0466V24.8006L16.1074 25.7398L14.2289 23.8613L12.3504 25.7398L11.4111 24.8006Z" fill="#D8B18B"/>
<path d="M10.002 14.4688H18.4552V21.9827H10.002V14.4688Z" fill="#E8D5B2"/>
<path d="M14.6985 19.6359H11.8808C11.7562 19.6359 11.6368 19.6854 11.5487 19.7734C11.4606 19.8615 11.4111 19.981 11.4111 20.1055C11.4111 20.2301 11.4606 20.3495 11.5487 20.4376C11.6368 20.5257 11.7562 20.5751 11.8808 20.5751H14.6985C14.8231 20.5751 14.9425 20.5257 15.0306 20.4376C15.1186 20.3495 15.1681 20.2301 15.1681 20.1055C15.1681 19.981 15.1186 19.8615 15.0306 19.7734C14.9425 19.6854 14.8231 19.6359 14.6985 19.6359ZM16.577 19.6359H16.1074C15.9828 19.6359 15.8634 19.6854 15.7753 19.7734C15.6872 19.8615 15.6377 19.981 15.6377 20.1055C15.6377 20.2301 15.6872 20.3495 15.7753 20.4376C15.8634 20.5257 15.9828 20.5751 16.1074 20.5751H16.577C16.7016 20.5751 16.821 20.5257 16.9091 20.4376C16.9971 20.3495 17.0466 20.2301 17.0466 20.1055C17.0466 19.981 16.9971 19.8615 16.9091 19.7734C16.821 19.6854 16.7016 19.6359 16.577 19.6359ZM13.7593 16.8182H16.577C16.7016 16.8182 16.821 16.7687 16.9091 16.6806C16.9971 16.5925 17.0466 16.4731 17.0466 16.3485C17.0466 16.224 16.9971 16.1045 16.9091 16.0165C16.821 15.9284 16.7016 15.8789 16.577 15.8789H13.7593C13.6347 15.8789 13.5153 15.9284 13.4272 16.0165C13.3391 16.1045 13.2896 16.224 13.2896 16.3485C13.2896 16.4731 13.3391 16.5925 13.4272 16.6806C13.5153 16.7687 13.6347 16.8182 13.7593 16.8182ZM16.577 17.7574H15.6377C15.5132 17.7574 15.3937 17.8069 15.3057 17.895C15.2176 17.983 15.1681 18.1025 15.1681 18.227C15.1681 18.3516 15.2176 18.471 15.3057 18.5591C15.3937 18.6472 15.5132 18.6967 15.6377 18.6967H16.577C16.7016 18.6967 16.821 18.6472 16.9091 18.5591C16.9971 18.471 17.0466 18.3516 17.0466 18.227C17.0466 18.1025 16.9971 17.983 16.9091 17.895C16.821 17.8069 16.7016 17.7574 16.577 17.7574ZM11.8808 18.6967H12.82C12.9446 18.6967 13.064 18.6472 13.1521 18.5591C13.2402 18.471 13.2896 18.3516 13.2896 18.227C13.2896 18.1025 13.2402 17.983 13.1521 17.895C13.064 17.8069 12.9446 17.7574 12.82 17.7574H11.8808C11.7562 17.7574 11.6368 17.8069 11.5487 17.895C11.4606 17.983 11.4111 18.1025 11.4111 18.227C11.4111 18.3516 11.4606 18.471 11.5487 18.5591C11.6368 18.6472 11.7562 18.6967 11.8808 18.6967ZM11.8808 16.8182H12.3504C12.4749 16.8182 12.5944 16.7687 12.6825 16.6806C12.7705 16.5925 12.82 16.4731 12.82 16.3485C12.82 16.224 12.7705 16.1045 12.6825 16.0165C12.5944 15.9284 12.4749 15.8789 12.3504 15.8789H11.8808C11.7562 15.8789 11.6368 15.9284 11.5487 16.0165C11.4606 16.1045 11.4111 16.224 11.4111 16.3485C11.4111 16.4731 11.4606 16.5925 11.5487 16.6806C11.6368 16.7687 11.7562 16.8182 11.8808 16.8182ZM13.9001 17.8983C13.8129 17.9858 13.7625 18.1035 13.7593 18.227C13.7593 18.3538 13.8062 18.4712 13.9001 18.5558C13.9882 18.6422 14.1055 18.6925 14.2289 18.6967C14.3532 18.6949 14.4723 18.6463 14.5623 18.5605C14.6481 18.4705 14.6968 18.3514 14.6985 18.227C14.6968 18.1027 14.6481 17.9836 14.5623 17.8936C14.4713 17.8101 14.3524 17.7638 14.2289 17.7638C14.1054 17.7638 13.9864 17.8101 13.8954 17.8936L13.9001 17.8983Z" fill="#D4C3A5"/>
<path d="M11.4111 0.380859H17.0466V7.89485H11.4111V0.380859Z" fill="#F4D5BD"/>
<path d="M17.0466 11.1819V7.89453H11.4111V11.1819L12.3504 10.2427L14.2289 12.1211L16.1074 10.2427L17.0466 11.1819Z" fill="#D8B18B"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M28.8 40.371L9.6 7.113H19.2l19.201 33.258h-9.6z" fill="#FF4081"/><path d="M28.8 40.371l4.8-8.314 4.802 8.314H28.8z" fill="#fff" fill-opacity=".2"/><path d="M24 32.057l4.8 8.314 4.8-8.314H24z" fill="#fff" fill-opacity=".1"/><path d="M19.2 23.742l4.8 8.315 4.8-8.315h-9.6z" fill="#000" fill-opacity=".1"/><path d="M19.2 23.742l4.8-8.314 4.8 8.314h-9.6z" fill="#000" fill-opacity=".2"/><path d="M14.4 15.428l4.8 8.314 4.8-8.314h-9.6z" fill="#000" fill-opacity=".3"/><path d="M14.4 15.428l4.8-8.315 4.8 8.315h-9.6z" fill="#000" fill-opacity=".4"/><path d="M9.599 7.113l4.801 8.315 4.8-8.315H9.6z" fill="#000" fill-opacity=".5"/><path d="M9.599 40.371L-.002 23.742l4.8-8.314L19.2 40.37H9.599z" fill="#536DFE"/><path d="M9.599 40.371l4.801-8.314 4.8 8.314H9.6z" fill="#fff" fill-opacity=".2"/><path d="M4.799 32.057l4.8 8.314 4.801-8.314H4.8z" fill="#fff" fill-opacity=".1"/><path d="M-.002 23.742l4.8 8.315 4.8-8.315h-9.6z" fill="#000" fill-opacity=".1"/><path d="M-.002 23.742l4.8-8.314 4.8 8.314h-9.6z" fill="#000" fill-opacity=".2"/><path d="M9.599 23.742l-4.8-8.314 4.8-8.315 4.801 8.315L9.6 23.742z" fill="#303F9F"/><path d="M14.4 15.428L9.6 7.113l-4.8 8.315H14.4z" fill="#000" fill-opacity=".2"/><path d="M38.401 40.371l-4.8-8.314 4.8-8.315 4.8 8.315-4.8 8.314z" fill="#3F51B5"/><path d="M43.201 32.057l-4.8 8.314-4.8-8.314h9.6z" fill="#000" fill-opacity=".2"/><path d="M43.201 32.057L28.8 7.113h9.601l9.602 16.63-4.802 8.314z" fill="#7986CB"/><path d="M38.401 23.742l4.8 8.315 4.801-8.315h-9.6z" fill="#fff" fill-opacity=".2"/><path d="M38.401 23.742l4.8-8.314 4.801 8.314h-9.6z" fill="#fff" fill-opacity=".1"/><path d="M33.6 15.428l4.801-8.315 4.8 8.315h-9.6z" fill="#000" fill-opacity=".1"/><path d="M28.8 7.113l4.8 8.315 4.801-8.315H28.8z" fill="#000" fill-opacity=".2"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(0 7)" d="M0 0H48V33.375H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M23.757 0l20.756 11.984V35.95L23.758 47.935 3 35.951V11.984L23.757 0z" fill="#673AB8"/><path d="M8.654 35.753c2.76 3.532 11.529.84 19.808-5.628S41.474 15.8 38.714 12.267s-11.529-.84-19.808 5.628S5.894 32.22 8.654 35.753zm1.178-.92c-.915-1.172-.515-3.472 1.229-6.366 1.836-3.048 4.952-6.413 8.766-9.393 3.814-2.98 7.833-5.19 11.235-6.234 3.23-.991 5.558-.823 6.473.348.916 1.172.516 3.471-1.228 6.366-1.836 3.048-4.952 6.413-8.766 9.393-3.814 2.98-7.833 5.19-11.235 6.233-3.23.992-5.558.824-6.474-.348z" fill="#fff"/><path d="M38.714 35.753c2.76-3.532-1.973-11.39-10.252-17.858-8.28-6.468-17.049-9.16-19.808-5.628-2.76 3.532 1.973 11.39 10.252 17.858 8.28 6.469 17.049 9.16 19.808 5.628zm-1.178-.92c-.916 1.17-3.244 1.339-6.474.347-3.402-1.044-7.42-3.254-11.235-6.233-3.814-2.98-6.93-6.345-8.766-9.393-1.744-2.895-2.144-5.194-1.229-6.366.916-1.171 3.243-1.34 6.474-.348 3.402 1.044 7.42 3.254 11.235 6.234 3.814 2.98 6.93 6.345 8.766 9.393 1.744 2.894 2.144 5.194 1.228 6.365z" fill="#fff"/><path d="M23.684 27.19a3.179 3.179 0 1 0 0-6.359 3.179 3.179 0 0 0 0 6.358z" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3)" d="M0 0H41.514V48H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1 @@
<svg fill="none" width="48" height="48" viewBox="0 0 917 1000" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m249.557 144.582 194.171 132.54c4.383 2.918 9.502 4.516 14.755 4.606 5.261-.038 10.394-1.641 14.755-4.606l194.319-132.986c7.55-5.406 11.714-14.418 10.957-23.717-.757-9.298-6.322-17.507-14.646-21.6024l-194.171-96.13614c-7.366-3.573948-15.947-3.573948-23.313 0l-193.581 96.13614c-8.474 4.1174-14.113 12.4854-14.783 21.9354-.67 9.451 3.73 18.541 11.537 23.83zm274.879 174.144c.016 8.789 4.318 17.01 11.509 21.991l155.662 106.389c9.965 6.87 23.298 6.012 32.313-2.081l130.579-116.789c5.819-5.199 9.051-12.729 8.823-20.56s-3.892-15.158-10.004-20.005l-124.677-99.702c-9.062-7.199-21.704-7.68-31.28-1.189l-161.416 110.401c-7.064 4.89-11.35 12.914-11.509 21.545zm-387.163 144.724c6.292 5.652 9.526 13.988 8.706 22.437-.817 8.499-5.726 16.052-13.132 20.208l-92.9545 55.72c-9.4227 5.633-21.32 4.82-29.90183-2.041-8.5818-6.861-12.06543-18.346-8.75546-28.865l34.37839-108.172c2.6969-8.57 9.5328-15.175 18.1483-17.533 8.609-2.505 17.8924-.309 24.4928 5.795zm504.168 11.293-168.056-115.007c-8.931-6.01-20.578-6.01-29.509 0l-168.056 115.007c-6.684 4.626-10.919 12.061-11.509 20.208-.435 8.203 2.816 16.169 8.853 21.693l167.909 150.222c4.842 4.319 11.089 6.698 17.558 6.687 6.465-.002 12.708-2.38 17.558-6.687l167.908-150.222c6.056-5.501 9.265-13.5 8.705-21.693-.469-8.146-4.666-15.612-11.361-20.208zm-448.247-29.718-130.4316-116.79c-5.8687-5.331-9.1073-12.995-8.8528-20.95.1419-7.841 3.7705-15.204 9.8856-20.06l124.6768-100.296c9.126-7.179 21.793-7.658 31.428-1.189l161.269 110.401c7.484 4.908 11.998 13.293 11.998 22.288 0 8.994-4.514 17.38-11.998 22.288l-155.515 106.388c-10.025 6.841-23.376 5.985-32.46-2.08zm669.715 167.756-132.792-79.495c-9.862-5.943-22.415-4.739-30.985 2.972l-162.301 144.873c-6.846 6.114-10.062 15.362-8.499 24.441 1.563 9.08 7.681 16.698 16.171 20.135l225.157 91.233c3.088 1.283 6.397 1.939 9.738 1.932 10.449.033 19.936-6.142 24.197-15.751l69.79-156.314c5.68-12.37 1.157-27.062-10.476-34.026zm18.443-190.043 34.379 108.171h-.295c2.542 8.091 1.097 16.919-3.889 23.761-4.986 6.841-12.915 10.876-21.342 10.86-4.728.016-9.37-1.269-13.427-3.715l-93.102-55.72c-7.254-4.243-11.992-11.789-12.689-20.208-.87-8.456 2.373-16.814 8.705-22.436l59.019-52.6c6.668-5.976 15.881-8.156 24.493-5.795 8.609 2.459 15.423 9.098 18.148 17.682zm-492.511 282.761c1.587-9.042-1.597-18.266-8.41-24.368l-162.302-144.873c-8.57-7.711-21.123-8.915-30.985-2.972l-132.7921 79.495c-11.4977 6.995-16.0467 21.502-10.6233 33.878l69.9374 156.314c5.794 13.034 20.774 19.134 33.936 13.818l225.009-91.232c8.492-3.407 14.632-10.995 16.23-20.06zm79.675 44.577 180.598 73.105c8.83 3.779 14.93 12.084 15.935 21.694 1.143 9.729-3.178 19.291-11.214 24.814l-180.745 125.556c-4.331 3.043-9.473 4.7-14.754 4.755-5.277-.082-10.411-1.737-14.755-4.755l-180.597-125.556c-8.066-5.508-12.439-15.061-11.362-24.814 1.206-9.71 7.526-18.006 16.526-21.694l180.597-73.105c6.351-2.532 13.421-2.532 19.771 0z" fill="#bf4722" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,6 @@
<svg viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M700 0H100C44.772 0 0 44.772 0 100v600c0 55.228 44.772 100 100 100h600c55.228 0 100-44.772 100-100V100C800 44.772 755.228 0 700 0Z" fill="#212121"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M587.947 527.768c4.254 54.65 4.254 80.268 4.254 108.232H465.756c0-6.091.109-11.663.219-17.313.342-17.564.699-35.88-2.147-72.868-3.761-54.152-27.08-66.185-69.957-66.185H195v-98.525h204.889c54.16 0 81.241-16.476 81.241-60.098 0-38.357-27.081-61.601-81.241-61.601H195V163h227.456C545.069 163 606 220.912 606 313.42c0 69.193-42.877 114.319-100.799 121.84 48.895 9.777 77.48 37.605 82.746 92.508Z" fill="#fff"/>
<path d="M195 636v-73.447h133.697c22.332 0 27.181 16.563 27.181 26.441V636H195Z" fill="#fff"/>
<path d="M194.5 636v.5h161.878v-47.506c0-5.006-1.226-11.734-5.315-17.224-4.108-5.515-11.059-9.717-22.366-9.717H194.5V636Z" stroke="#fff" stroke-opacity=".8"/>
</svg>

After

Width:  |  Height:  |  Size: 958 B

@@ -0,0 +1 @@
<svg width="50" height="50" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M25 49c13.255 0 24-10.745 24-24S38.255 1 25 1 1 11.745 1 25s10.745 24 24 24z" fill="#00838F" stroke="#00838F" stroke-width=".308"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.355 28.86l6.064-.508 1.14 2.633-3.913 3.882-3.29 4.626.433 5.617c-3.218-.786-5.361-1.56-6.43-2.324-1.068-.763-2.215-1.861-3.441-3.293l5.745-6.843h2.514l1.178-3.79z" fill="#FBB526"/><path fill-rule="evenodd" clip-rule="evenodd" d="M25.36 7.404a2.993 2.993 0 0 0-1.916 5.292v4.045h3.95v-4.15a2.993 2.993 0 0 0-2.035-5.187zm-1.318 2.992a1.317 1.317 0 1 1 2.634 0 1.317 1.317 0 0 1-2.634 0z" fill="#fff"/><path d="M27.373 37.265l-2.953 2.83 1.16 1.21 2.083-1.997 1.866.978 5.01.68.225-1.661-4.718-.64-2.673-1.4z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M2.736 25C2.736 12.704 12.704 2.736 25 2.736S47.264 12.704 47.264 25 37.296 47.264 25 47.264 2.736 37.296 2.736 25zM25 4.41C13.63 4.411 4.41 13.63 4.41 25a20.51 20.51 0 0 0 5.1 13.565l5.418-7.22h3.086l.255-2.69 3.259-.618v-9.62h-3.352V16.74h14.364v1.675H29.31V30.52l2.17.222 1.761 2.328h1.629l5.653 5.457A20.51 20.51 0 0 0 45.59 25c0-11.37-9.218-20.589-20.59-20.589zm7.43 30.364l-.023-.03h-2.293v-1.676h1.024l-.556-.735-4.404-.45-.974 2.587-2.52 1.545-.875-1.43 2.03-1.243 1.225-3.257.847.086-.694-1.129-5.398 1.024-.63 6.639-2.977 2.581-1.098-1.266 2.47-2.142.271-2.86h-2.088l-5.083 6.777A20.52 20.52 0 0 0 25 45.59a20.52 20.52 0 0 0 14.37-5.846l-5.178-4.998h-1.723l-.04.03zm-7.909-7.306l-1.556.296v-9.349h4.908v11.748l-1.834-2.983-.082.016v-3.993h-1.436v4.265z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128" height="128" width="128">
<rect fill="#F03E2F" height="128" width="128"></rect>
<path fill="white" d="M39.4229 33.1629C39.4229 44.1614 46.3362 50.7055 60.1767 54.1563L74.8429 57.4971C87.9417 60.453 95.9185 67.7945 95.9185 79.7554C96.0204 84.9662 94.296 90.053 91.0345 94.1634C91.0345 82.23 84.751 75.7822 69.595 71.9052L55.1947 68.6881C43.6633 66.1035 34.7628 60.068 34.7628 47.076C34.7021 42.0589 36.3415 37.1644 39.4229 33.1629Z"></path>
<path fill="#F9B1AB" d="M82.0221 76.827C88.2776 80.759 91.0205 86.2583 91.0205 94.1497C85.8426 100.666 76.7462 104.323 66.0545 104.323C48.0576 104.323 35.4626 95.6207 32.6637 80.4978H49.9468C52.172 87.4406 58.0636 90.6577 65.9285 90.6577C75.5287 90.6577 81.9102 85.6258 82.0361 76.7995"></path>
<path fill="#F9B1AB" d="M48.4074 49.4682C45.5509 47.8004 43.2073 45.404 41.6255 42.5332C40.0437 39.6624 39.2825 36.4244 39.423 33.1629C44.419 26.7013 53.1095 22.7556 63.7033 22.7556C82.0361 22.7556 92.6439 32.2693 95.2608 45.66H78.6354C76.8021 40.3807 72.212 36.27 63.8433 36.27C54.9008 36.27 48.7992 41.3843 48.4494 49.4682"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
<svg width="47" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M39.792 6.09C35.507-.027 27.044-1.84 20.924 2.048L10.177 8.88a12.297 12.297 0 0 0-5.57 8.237 12.922 12.922 0 0 0 1.28 8.315 12.313 12.313 0 0 0-1.844 4.596c-.619 3.47.19 7.043 2.244 9.91 4.287 6.119 12.75 7.931 18.869 4.042l10.747-6.831a12.295 12.295 0 0 0 5.57-8.237 12.927 12.927 0 0 0-1.28-8.315A12.31 12.31 0 0 0 42.037 16c.619-3.47-.19-7.043-2.245-9.91" fill="#FF3E00"/><path d="M19.873 40.52a8.55 8.55 0 0 1-9.165-3.388 7.866 7.866 0 0 1-1.35-5.962c.062-.339.148-.674.257-1.001l.202-.616.55.404a13.881 13.881 0 0 0 4.206 2.095l.4.121-.037.398a2.4 2.4 0 0 0 .433 1.594 2.575 2.575 0 0 0 2.76 1.022 2.37 2.37 0 0 0 .66-.29l10.75-6.833a2.233 2.233 0 0 0 1.011-1.492 2.377 2.377 0 0 0-.407-1.797 2.577 2.577 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.289L22.62 26.65c-.675.428-1.411.75-2.183.956a8.55 8.55 0 0 1-9.166-3.388 7.866 7.866 0 0 1-1.35-5.962 7.394 7.394 0 0 1 3.35-4.954l10.75-6.834a7.844 7.844 0 0 1 2.185-.957 8.55 8.55 0 0 1 9.165 3.388 7.866 7.866 0 0 1 1.35 5.962c-.062.34-.148.674-.256 1.001l-.203.616-.55-.403a13.87 13.87 0 0 0-4.206-2.096l-.4-.121.037-.398a2.404 2.404 0 0 0-.433-1.594 2.575 2.575 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.29l-10.75 6.833a2.229 2.229 0 0 0-1.01 1.492c-.112.63.034 1.277.406 1.797a2.577 2.577 0 0 0 2.76 1.023c.234-.063.457-.16.661-.29l4.102-2.607a7.834 7.834 0 0 1 2.183-.957 8.55 8.55 0 0 1 9.165 3.388 7.865 7.865 0 0 1 1.35 5.962 7.398 7.398 0 0 1-3.35 4.955l-10.75 6.833a7.842 7.842 0 0 1-2.185.958" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3.84)" d="M0 0H38.4V46.08H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16382.881px" height="16382.881px" viewBox="0 0 16382.881 16382.881" enable-background="new 0 0 16382.881 16382.881"
xml:space="preserve">
<title>logo</title>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-753.6542" y1="-558.0049" x2="-511.421" y2="-558.0049" gradientTransform="matrix(20.4786 0 0 20.4786 15773.2188 19686.7227)">
<stop offset="0" style="stop-color:#007C42"/>
<stop offset="1" style="stop-color:#6CCE7A"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M4191.296,10712.322c-57.461,23.9-120.146,32.449-181.906,24.805
c-773.881-103.355-1509.856-397.746-2141.531-856.611c-648.513-472.885-1164-1105.141-1496.591-1835.602
c-53.005-116.381-39.096-252.307,36.381-355.543c985.096-1343.061,2610.36-2060.569,4266.526-1883.555
c130.381,13.449,242.513,98.186,291.049,219.941c305.934,762.764,404.635,1592.887,286.09,2406.121
c-115.52,789.172-430.254,1535.885-914.492,2169.643C4298.488,10650.195,4248.412,10688.322,4191.296,10712.322z"/>
<path fill="#FFFFFF" d="M4191.296,9075.17c-12.973-1.172-25.75-3.949-38.035-8.273c-99.968-35.08-152.569-144.557-117.49-244.525
c0.027-0.072,0.052-0.146,0.078-0.221c61.186-168.676,4.961-246.4-13.229-272.86c-148.833-206.71-649.901-226.554-858.267-224.899
c-510.99,4.959-854.959-120.721-1018.675-372.082c-163.716-251.359-100.875-519.26-42.996-649.9
c48.825-94.024,164.625-130.664,258.647-81.836c85.419,44.355,124.742,145.146,91.936,235.629
c-1.654,6.613-66.148,168.679,13.229,287.742c79.377,119.066,334.045,200.097,694.55,198.441
c593.677-4.961,987.254,125.682,1174.123,385.311c122.373,171.985,145.523,388.619,62.84,621.789
C4368.065,9035.451,4281.399,9088.146,4191.296,9075.17z"/>
<path opacity="0.7" fill="#FFFFFF" enable-background="new " d="M2663.284,7070.893
c169.636-57.978,348.343-84.867,527.528-79.377c396.886,11.574,631.71,145.524,879.766,469.647
c25.541,33.194,61.756,56.56,102.527,66.146c97.105,21.931,193.602-39.016,215.527-136.121
c11.683-51.733-0.012-105.985-31.967-148.313c-395.232-515.951-805.35-600.287-1155.932-610.211
c-227.275-7.728-453.959,27.621-668.091,104.185c-91.777,36.086-136.936,139.729-100.875,231.514
C2467.904,7060.111,2571.059,7105.796,2663.284,7070.893z"/>
<path opacity="0.7" fill="#FFFFFF" enable-background="new " d="M3829.137,9189.271
c-169.855,56.932-348.472,83.246-527.528,77.725c-396.886-11.578-722.663-181.908-970.718-506.029
c-65.025-75.381-178.845-83.773-254.224-18.746c-69.058,59.57-82.738,161.34-31.864,237.033
c284.436,372.08,625.096,539.102,922.76,606.904c106.371,24.441,214.998,37.74,324.124,39.688
c227.085,6.832,453.505-27.918,668.092-102.529c92.244-36.076,137.778-140.098,101.702-232.342
C4025.406,9198.729,3921.383,9153.193,3829.137,9189.271L3829.137,9189.271z"/>
<path fill="#3AA860" d="M12854.218,9688.209H8835.396c-96.75,0-175.184-78.432-175.184-175.184s78.434-175.186,175.184-175.186
h4018.822c96.752,0,175.184,78.434,175.184,175.186S12950.97,9688.209,12854.218,9688.209L12854.218,9688.209z"/>
<path fill="#FFFFFF" d="M8243.106,8124.061c-83.421-70.908-216.896-123.047-410.851-162.672
c-68.852-13.235-136.509-32.067-202.296-56.309c-52.138-20.855-72.994-39.625-81.335-52.139
c-13.252-19.658-19.833-43.056-18.771-66.736c0-20.855,4.172-52.141,56.311-79.252c55.21-26.412,116.148-38.6,177.271-35.453
c54.981-1.639,109.879,5.4,162.67,20.855c46.125,16.496,88.11,42.826,123.047,77.164c39.223,36.391,90.41,57.164,143.902,58.396
c33.648,0.91,66.654-9.358,93.848-29.199c34.096-24.937,54.855-64.146,56.311-106.36c-0.506-38.345-14.541-75.275-39.625-104.277
c-60.445-70.131-137.027-124.523-223.153-158.5c-99.603-39.002-205.877-58.133-312.829-56.309
c-91.018-0.881-181.447,14.664-266.949,45.881c-81.441,26.905-154.182,75.158-210.639,139.729
c-54.58,66.213-83.459,149.883-81.334,235.666c0,225.235,175.185,373.31,519.297,440.047
c120.959,22.941,208.554,50.053,256.52,83.42c47.968,33.369,64.652,62.568,64.652,102.191c0,39.625-20.855,75.08-64.652,102.192
c-43.796,27.11-118.875,43.794-208.553,43.794c-66.498,0.443-132.422-12.316-193.955-37.539
c-50.361-19.326-96.424-48.381-135.56-85.506c-66.737-68.822-145.987-79.25-231.494-14.599
c-41.321,29.054-65.54,76.712-64.651,127.218c-0.793,38.972,14.256,76.601,41.711,104.276
c72.571,68.375,159.5,119.68,254.435,150.156l0,0c109.301,37.918,224.254,56.961,339.94,56.313
c107.158,1.945,213.592-17.926,312.832-58.395c82.017-34.082,152.318-91.34,202.294-164.758
c47.371-68.582,72.16-150.237,70.908-233.579C8374.509,8299.932,8327.013,8194.982,8243.106,8124.061z"/>
<path fill="#FFFFFF" d="M9290.042,7673.586c54.916-0.777,109.457,9.141,160.586,29.197c48.457,21.176,91.834,52.463,127.219,91.762
c30.396,37.82,76.623,59.393,125.131,58.396c28.408,0.303,56.17-8.463,79.25-25.025c43.338-25.982,70.158-72.525,70.908-123.049
c0.754-33.059-11.189-65.15-33.369-89.676c-130.795-156.744-325.613-245.717-529.725-241.922
c-131.465-2.219-261.113,30.914-375.396,95.934c-110.438,62.479-200.848,155.06-260.691,266.947
c-62.029,118.289-93.568,250.186-91.764,383.738c-1.621,133.122,30.676,264.466,93.85,381.651
c63.391,111.345,155.604,203.556,266.947,266.946c115.611,65.711,246.598,99.539,379.568,98.023
c193.955,0,356.625-60.48,485.928-183.527c26.264-24.184,41.361-58.148,41.711-93.848c-1.609-47.101-24.654-90.884-62.566-118.878
c-58.395-43.795-131.387-39.625-204.381,12.514s-152.244,83.42-260.691,83.42c-76.904,0.486-152.529-19.68-218.982-58.393
c-62.852-37.467-114.104-91.605-148.072-156.416c-36.965-71.484-55.598-151.029-54.223-231.494
c0-135.56,39.623-244.006,114.703-325.344C9071.062,7713.211,9169.081,7673.586,9290.042,7673.586z"/>
<path fill="#FFFFFF" d="M11484.022,7427.492c-31.891-32.121-75.725-49.504-120.961-47.969
c-43.418-0.484-85.313,15.975-116.789,45.883c-32.121,31.891-49.504,75.723-47.965,120.961v723.68
c0.527,51.977-14.746,102.887-43.799,145.988c-30.934,46.467-74.289,83.316-125.131,106.362
c-56.539,27.243-118.691,40.817-181.441,39.624c-114.705,0-202.297-31.283-271.121-95.933
c-68.82-64.653-100.105-160.589-100.105-289.89v-629.832c10.287-91.568-55.604-174.14-147.174-184.424
c-91.566-10.287-174.137,55.604-184.426,147.174c-1.389,12.377-1.389,24.873,0,37.25v629.832
c0,143.901,27.115,269.032,83.422,373.309c53.686,99.838,134.711,182.307,233.58,237.752
c103.283,54.963,218.783,82.939,335.771,81.336c86.42,0.398,172.102-15.891,252.348-47.967
c55.035-20.092,106.449-48.969,152.244-85.508c8.377,27.564,23.418,52.635,43.795,72.994c30.75,31.264,72.945,48.594,116.791,47.967
c45.238,1.537,89.07-15.846,120.961-47.967c30.225-32.117,46.691-74.781,45.881-118.875V7546.367
C11531.171,7502.193,11514.64,7459.359,11484.022,7427.492L11484.022,7427.492z"/>
<path fill="#FFFFFF" d="M12353.69,8528.652h-64.652c-16.684,0-39.625-4.17-62.566-43.795
c-22.49-42.279-33.291-89.797-31.281-137.646V6945.734c1.422-43.584-15.174-85.828-45.883-116.791
c-30.82-30.922-73.162-47.558-116.789-45.881c-43.586-1.424-85.828,15.174-116.791,45.881
c-30.922,30.819-47.559,73.164-45.881,116.791v1401.477c-1.457,88.953,16.33,177.17,52.139,258.607
c31.369,73.734,82.641,137.279,148.072,183.527c64.445,44.129,140.879,67.422,218.98,66.736h4.172
c52.836,2.123,104.893-13.273,148.072-43.795c38.93-27.105,62.264-71.439,62.566-118.877c1.199-42.775-13.684-84.451-41.711-116.789
C12434.901,8545.325,12395.169,8527.75,12353.69,8528.652z"/>
<path fill="#FFFFFF" d="M13121.165,8528.652h-62.566c-16.684,0-39.625-4.17-62.564-43.795
c-22.492-42.279-33.293-89.797-31.285-137.646V6945.734c0.488-43.418-15.973-85.314-45.881-116.791
c-30.961-30.707-73.205-47.306-116.789-45.881c-43.715-2.207-86.266,14.51-116.789,45.881
c-30.922,30.819-47.559,73.164-45.883,116.791v1401.477c-1.119,88.715,15.914,176.719,50.053,258.607
c32.133,73.875,84.107,137.402,150.16,183.527c64.443,44.129,140.877,67.422,218.98,66.736h4.172
c52.191,2.135,103.59-13.285,145.986-43.795c39.785-26.346,63.375-71.166,62.566-118.877c1.982-42.531-12.17-84.244-39.627-116.789
C13203.646,8545.232,13163.253,8527.703,13121.165,8528.652z"/>
<path fill="#FFFFFF" d="M14578.954,7398.297c-23.686-10.209-49.293-15.189-75.08-14.602c-41.709,0-98.021,16.688-133.475,93.85
l-381.652,863.41l-435.875-867.582c-24.313-55.266-79.361-90.596-139.73-89.678c-22.164-0.109-44.133,4.143-64.652,12.514
c-26.777,12.59-50.318,31.137-68.822,54.225c-31.834,44.748-35.863,103.574-10.428,152.244l569.352,1080.309l-248.18,561.004
c-12.082,23.949-18.504,50.348-18.771,77.168c0,41.711,16.684,95.934,98.021,131.387c23.383,11.582,48.994,17.984,75.076,18.77
c41.715,0,100.107-16.684,131.391-100.105l784.158-1764.359c11.402-24.191,17.787-50.439,18.771-77.164
C14679.06,7487.973,14660.29,7431.664,14578.954,7398.297z"/>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 166 155.3"><defs><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="27.5" y1="3" x2="152" y2="63.5"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="95.8" y1="32.6" x2="74" y2="105.2"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="18.4" y1="64.2" x2="144.3" y2="149.8"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="75.2" y1="74.5" x2="24.4" y2="260.8"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient></defs><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" fill="#76b3e1"/><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" opacity=".3" fill="url(#a)"/><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" fill="#518ac8"/><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" opacity=".3" fill="url(#b)"/><path d="M134 80a45 45 0 00-48-15L24 85 4 120l112 19 20-36c4-7 3-15-2-23z" fill="url(#c)"/><path d="M114 115a45 45 0 00-48-15L4 120s53 40 94 30l3-1c17-5 23-21 13-34z" fill="url(#d)"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
<circle cx="24" cy="24" r="24" fill="#000"/>
<path fill="#fff" d="M20.27 29H32l-6.34 7H14l6.27-7zM40 21H15.45L9 27h24.55L40 21zM23.32 12H35l-6.3 7H17l6.32-7z"/>
</svg>

After

Width:  |  Height:  |  Size: 248 B

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="39" height="48" fill="none">
<mask id="a" width="39" height="48" x="0" y="0" maskUnits="userSpaceOnUse">
<path fill="#fff" d="M1.48 43.97L0 4.58A2.4 2.4 0 0 1 2.25 2.1L35.75 0a2.4 2.4 0 0 1 2.55 2.4v42.92a2.4 2.4 0 0 1-2.5 2.4L3.76 46.28a2.4 2.4 0 0 1-2.29-2.3z"/>
</mask>
<g mask="url(#a)">
<path fill="#FF4785" d="M1.47 43.97L-.01 4.58A2.4 2.4 0 0 1 2.24 2.1L35.74 0a2.4 2.4 0 0 1 2.55 2.4v42.92a2.4 2.4 0 0 1-2.5 2.4L3.75 46.28a2.4 2.4 0 0 1-2.3-2.3z"/>
<path fill="#fff" d="M28.26 5.87l.23-5.5L33.1 0l.2 5.68a.36.36 0 0 1-.58.29l-1.77-1.4-2.1 1.6a.36.36 0 0 1-.58-.3zm-5.88 12.12c0 .93 6.29.48 7.13-.17 0-6.36-3.41-9.7-9.66-9.7-6.24 0-9.74 3.4-9.74 8.48 0 8.86 11.96 9.03 11.96 13.87 0 1.35-.67 2.16-2.13 2.16-1.9 0-2.66-.97-2.57-4.28 0-.72-7.26-.94-7.48 0-.57 8.01 4.43 10.33 10.14 10.33 5.54 0 9.88-2.96 9.88-8.3 0-9.5-12.14-9.24-12.14-13.95 0-1.9 1.42-2.16 2.26-2.16.89 0 2.48.15 2.35 3.72z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 980 B

@@ -0,0 +1 @@
<svg width="47" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M39.792 6.09C35.507-.027 27.044-1.84 20.924 2.048L10.177 8.88a12.297 12.297 0 0 0-5.57 8.237 12.922 12.922 0 0 0 1.28 8.315 12.313 12.313 0 0 0-1.844 4.596c-.619 3.47.19 7.043 2.244 9.91 4.287 6.119 12.75 7.931 18.869 4.042l10.747-6.831a12.295 12.295 0 0 0 5.57-8.237 12.927 12.927 0 0 0-1.28-8.315A12.31 12.31 0 0 0 42.037 16c.619-3.47-.19-7.043-2.245-9.91" fill="#FF3E00"/><path d="M19.873 40.52a8.55 8.55 0 0 1-9.165-3.388 7.866 7.866 0 0 1-1.35-5.962c.062-.339.148-.674.257-1.001l.202-.616.55.404a13.881 13.881 0 0 0 4.206 2.095l.4.121-.037.398a2.4 2.4 0 0 0 .433 1.594 2.575 2.575 0 0 0 2.76 1.022 2.37 2.37 0 0 0 .66-.29l10.75-6.833a2.233 2.233 0 0 0 1.011-1.492 2.377 2.377 0 0 0-.407-1.797 2.577 2.577 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.289L22.62 26.65c-.675.428-1.411.75-2.183.956a8.55 8.55 0 0 1-9.166-3.388 7.866 7.866 0 0 1-1.35-5.962 7.394 7.394 0 0 1 3.35-4.954l10.75-6.834a7.844 7.844 0 0 1 2.185-.957 8.55 8.55 0 0 1 9.165 3.388 7.866 7.866 0 0 1 1.35 5.962c-.062.34-.148.674-.256 1.001l-.203.616-.55-.403a13.87 13.87 0 0 0-4.206-2.096l-.4-.121.037-.398a2.404 2.404 0 0 0-.433-1.594 2.575 2.575 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.29l-10.75 6.833a2.229 2.229 0 0 0-1.01 1.492c-.112.63.034 1.277.406 1.797a2.577 2.577 0 0 0 2.76 1.023c.234-.063.457-.16.661-.29l4.102-2.607a7.834 7.834 0 0 1 2.183-.957 8.55 8.55 0 0 1 9.165 3.388 7.865 7.865 0 0 1 1.35 5.962 7.398 7.398 0 0 1-3.35 4.955l-10.75 6.833a7.842 7.842 0 0 1-2.185.958" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3.84)" d="M0 0H38.4V46.08H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1 @@
<svg width="47" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M39.792 6.09C35.507-.027 27.044-1.84 20.924 2.048L10.177 8.88a12.297 12.297 0 0 0-5.57 8.237 12.922 12.922 0 0 0 1.28 8.315 12.313 12.313 0 0 0-1.844 4.596c-.619 3.47.19 7.043 2.244 9.91 4.287 6.119 12.75 7.931 18.869 4.042l10.747-6.831a12.295 12.295 0 0 0 5.57-8.237 12.927 12.927 0 0 0-1.28-8.315A12.31 12.31 0 0 0 42.037 16c.619-3.47-.19-7.043-2.245-9.91" fill="#FF3E00"/><path d="M19.873 40.52a8.55 8.55 0 0 1-9.165-3.388 7.866 7.866 0 0 1-1.35-5.962c.062-.339.148-.674.257-1.001l.202-.616.55.404a13.881 13.881 0 0 0 4.206 2.095l.4.121-.037.398a2.4 2.4 0 0 0 .433 1.594 2.575 2.575 0 0 0 2.76 1.022 2.37 2.37 0 0 0 .66-.29l10.75-6.833a2.233 2.233 0 0 0 1.011-1.492 2.377 2.377 0 0 0-.407-1.797 2.577 2.577 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.289L22.62 26.65c-.675.428-1.411.75-2.183.956a8.55 8.55 0 0 1-9.166-3.388 7.866 7.866 0 0 1-1.35-5.962 7.394 7.394 0 0 1 3.35-4.954l10.75-6.834a7.844 7.844 0 0 1 2.185-.957 8.55 8.55 0 0 1 9.165 3.388 7.866 7.866 0 0 1 1.35 5.962c-.062.34-.148.674-.256 1.001l-.203.616-.55-.403a13.87 13.87 0 0 0-4.206-2.096l-.4-.121.037-.398a2.404 2.404 0 0 0-.433-1.594 2.575 2.575 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.29l-10.75 6.833a2.229 2.229 0 0 0-1.01 1.492c-.112.63.034 1.277.406 1.797a2.577 2.577 0 0 0 2.76 1.023c.234-.063.457-.16.661-.29l4.102-2.607a7.834 7.834 0 0 1 2.183-.957 8.55 8.55 0 0 1 9.165 3.388 7.865 7.865 0 0 1 1.35 5.962 7.398 7.398 0 0 1-3.35 4.955l-10.75 6.833a7.842 7.842 0 0 1-2.185.958" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3.84)" d="M0 0H38.4V46.08H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1 @@
<svg width="47" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M39.792 6.09C35.507-.027 27.044-1.84 20.924 2.048L10.177 8.88a12.297 12.297 0 0 0-5.57 8.237 12.922 12.922 0 0 0 1.28 8.315 12.313 12.313 0 0 0-1.844 4.596c-.619 3.47.19 7.043 2.244 9.91 4.287 6.119 12.75 7.931 18.869 4.042l10.747-6.831a12.295 12.295 0 0 0 5.57-8.237 12.927 12.927 0 0 0-1.28-8.315A12.31 12.31 0 0 0 42.037 16c.619-3.47-.19-7.043-2.245-9.91" fill="#FF3E00"/><path d="M19.873 40.52a8.55 8.55 0 0 1-9.165-3.388 7.866 7.866 0 0 1-1.35-5.962c.062-.339.148-.674.257-1.001l.202-.616.55.404a13.881 13.881 0 0 0 4.206 2.095l.4.121-.037.398a2.4 2.4 0 0 0 .433 1.594 2.575 2.575 0 0 0 2.76 1.022 2.37 2.37 0 0 0 .66-.29l10.75-6.833a2.233 2.233 0 0 0 1.011-1.492 2.377 2.377 0 0 0-.407-1.797 2.577 2.577 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.289L22.62 26.65c-.675.428-1.411.75-2.183.956a8.55 8.55 0 0 1-9.166-3.388 7.866 7.866 0 0 1-1.35-5.962 7.394 7.394 0 0 1 3.35-4.954l10.75-6.834a7.844 7.844 0 0 1 2.185-.957 8.55 8.55 0 0 1 9.165 3.388 7.866 7.866 0 0 1 1.35 5.962c-.062.34-.148.674-.256 1.001l-.203.616-.55-.403a13.87 13.87 0 0 0-4.206-2.096l-.4-.121.037-.398a2.404 2.404 0 0 0-.433-1.594 2.575 2.575 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.29l-10.75 6.833a2.229 2.229 0 0 0-1.01 1.492c-.112.63.034 1.277.406 1.797a2.577 2.577 0 0 0 2.76 1.023c.234-.063.457-.16.661-.29l4.102-2.607a7.834 7.834 0 0 1 2.183-.957 8.55 8.55 0 0 1 9.165 3.388 7.865 7.865 0 0 1 1.35 5.962 7.398 7.398 0 0 1-3.35 4.955l-10.75 6.833a7.842 7.842 0 0 1-2.185.958" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3.84)" d="M0 0H38.4V46.08H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,19 @@
<svg width="48" height="48" viewBox="0 0 28 27" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-22.000000, -23.000000)">
<g transform="translate(23.000000, 24.000000)">
<path
d="M25.59375,11.968254 C25.59375,12.8799621 24.8542859,13.6190476 23.9421107,13.6190476 C23.0299354,13.6190476 2.97006457,13.6190476 2.05788934,13.6190476 C1.14571412,13.6190476 0.40625,12.8799621 0.40625,11.968254 C0.40625,11.368056 0.726725255,10.8426707 1.2059758,10.5537152 C0.964572543,10.2667081 0.819159836,9.89635858 0.819159836,9.49206349 C0.819159836,8.83899235 1.19858525,8.27449538 1.74912718,8.00677482 C1.68179398,7.82682944 1.64497951,7.63199747 1.64497951,7.42857143 C1.64497951,6.51686327 2.38444363,5.77777778 3.29661885,5.77777778 C3.37476958,5.77777778 3.45165256,5.78320283 3.52691139,5.79369671 C3.38061557,5.54732422 3.29661885,5.2596568 3.29661885,4.95238095 C3.29661885,4.04067279 4.03608297,3.3015873 4.9482582,3.3015873 C5.25569146,3.3015873 5.54350625,3.38554101 5.79000496,3.53176193 C5.7795057,3.45654163 5.77407787,3.37969801 5.77407787,3.3015873 C5.77407787,2.38987914 6.51354199,1.65079365 7.42571721,1.65079365 C7.73083366,1.65079365 8.0166262,1.73348677 8.26188409,1.87767313 C8.35426205,1.05344 9.0538542,0.412698413 9.90317623,0.412698413 C10.4521218,0.412698413 10.938518,0.680366532 11.2388423,1.09224331 C11.4679015,0.455411137 12.0775125,0 12.7935451,0 C13.5095776,0 14.1191887,0.455411137 14.3482478,1.09224331 C14.6485721,0.680366532 15.1349684,0.412698413 15.6839139,0.412698413 C16.533236,0.412698413 17.2328281,1.05344 17.3252061,1.87767313 C17.570464,1.73348677 17.8562565,1.65079365 18.161373,1.65079365 C19.0735482,1.65079365 19.8130123,2.38987914 19.8130123,3.3015873 C19.8130123,3.37969801 19.8075845,3.45654163 19.7970852,3.53176193 C20.0435839,3.38554101 20.3313987,3.3015873 20.638832,3.3015873 C21.5510072,3.3015873 22.2904713,4.04067279 22.2904713,4.95238095 C22.2904713,5.2596568 22.2064746,5.54732422 22.0601788,5.79369671 C22.1354376,5.78320283 22.2123206,5.77777778 22.2904713,5.77777778 C23.2026465,5.77777778 23.9421107,6.51686327 23.9421107,7.42857143 C23.9421107,7.63199747 23.9052962,7.82682944 23.837963,8.00677482 C24.3885049,8.27449538 24.7679303,8.83899235 24.7679303,9.49206349 C24.7679303,9.83184534 24.665222,10.1476506 24.489143,10.4101567 C25.1324705,10.635781 25.59375,11.2481763 25.59375,11.968254 Z"
stroke="#000000" stroke-width="0.8" fill="#FFFFFF" stroke-linejoin="round"></path>
<rect fill="#000000" x="8.53125" y="23.9365079" width="8.9375" height="2.06349206"></rect>
<path
d="M0,12.7936508 L26,12.7936508 C25.579783,19.7028245 19.9201192,25.1746032 13,25.1746032 C6.07988078,25.1746032 0.420216968,19.7028245 0,12.7936508 L0,12.7936508 Z"
stroke="#000000" stroke-width="0.8" fill="#1890FF"></path>
<path
d="M7.3125,7.01587302 C7.08813432,7.01587302 6.90625,6.83110164 6.90625,6.6031746 C6.90625,6.37524756 7.08813432,6.19047619 7.3125,6.19047619 C7.53686568,6.19047619 7.71875,6.37524756 7.71875,6.6031746 C7.71875,6.83110164 7.53686568,7.01587302 7.3125,7.01587302 Z M5.6875,9.07936508 C5.46313432,9.07936508 5.28125,8.89459371 5.28125,8.66666667 C5.28125,8.43873963 5.46313432,8.25396825 5.6875,8.25396825 C5.91186568,8.25396825 6.09375,8.43873963 6.09375,8.66666667 C6.09375,8.89459371 5.91186568,9.07936508 5.6875,9.07936508 Z M7.3125,10.7301587 C7.08813432,10.7301587 6.90625,10.5453874 6.90625,10.3174603 C6.90625,10.0895333 7.08813432,9.9047619 7.3125,9.9047619 C7.53686568,9.9047619 7.71875,10.0895333 7.71875,10.3174603 C7.71875,10.5453874 7.53686568,10.7301587 7.3125,10.7301587 Z M8.9375,7.42857143 C8.71313432,7.42857143 8.53125,7.24380006 8.53125,7.01587302 C8.53125,6.78794598 8.71313432,6.6031746 8.9375,6.6031746 C9.16186568,6.6031746 9.34375,6.78794598 9.34375,7.01587302 C9.34375,7.24380006 9.16186568,7.42857143 8.9375,7.42857143 Z M18.6875,9.07936508 C18.4631343,9.07936508 18.28125,8.89459371 18.28125,8.66666667 C18.28125,8.43873963 18.4631343,8.25396825 18.6875,8.25396825 C18.9118657,8.25396825 19.09375,8.43873963 19.09375,8.66666667 C19.09375,8.89459371 18.9118657,9.07936508 18.6875,9.07936508 Z"
fill="#000000"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff. Show more