Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(notifications): added notifications #176

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/lib/flow/logout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { goto } from '$app/navigation'
import { Query } from '$lib/api/executor.js'
import { ApiMutation } from '$lib/api/index.js'
import { useCustomerCtx } from '$lib/ctx/customer/index.svelte.js'
import { notifcation } from '$ui/core/Notifications/index.js'
import { notification } from '$ui/core/Notifications/index.js'

const mutateLogout = ApiMutation(
() => `mutation {
Expand All @@ -21,7 +21,7 @@ export function useLogoutFlow() {
.then(() => {
customer.reload()

notifcation.info("You've been logged out")
notification.info("You've been logged out")
})
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/ui/app/InsightCard/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default } from './InsightCard.svelte';
export { default } from './InsightCard.svelte'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was hanged automatically by eslint --fix

6 changes: 3 additions & 3 deletions src/lib/ui/app/PaymentForm/flow.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Query } from '$lib/api/executor.js'
import { useStripeCtx } from '$lib/ctx/stripe/index.js'
import { notifcation as notification } from '$ui/core/Notifications/index.js'
import { notification } from '$ui/core/Notifications/index.js'
Copy link
Contributor Author

@serafim-san serafim-san Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed typo in notification object

import type { ConfirmCardSetupData, SetupIntent, Stripe, Token } from '@stripe/stripe-js'
import { mutateSubscribe } from './api.js'
import { usePaymentFormCtx } from './state.js'
Expand Down Expand Up @@ -160,7 +160,7 @@ export function usePaymentFlow() {

if (error) {
notification.error(`Error during the payment`, {
description: error.message || 'Please try again or contact our support',
content: error.message || 'Please try again or contact our support',
duration: 10000,
})
trackEvent('payment_fail', { ...analytics, error_code: error.code || '3ds_error' })
Expand Down Expand Up @@ -212,7 +212,7 @@ export function usePaymentFlow() {
})
.catch((error) => {
notification.error(`Error during the payment`, {
description: 'Please try again or contact our support',
content: 'Please try again or contact our support',
})
trackEvent('payment_fail', { ...analytics, error_code: 'api_error' })

Expand Down
2 changes: 1 addition & 1 deletion src/lib/ui/app/PlanChangeDialog/PlanChangeDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { trackEvent } from '$lib/analytics/index.js'
import Dialog, { dialogs$, type TDialogProps } from '$ui/core/Dialog/index.js'
import Button from '$ui/core/Button/index.js'
import { notifcation as notification } from '$ui/core/Notifications/index.js'
import { notification } from '$ui/core/Notifications/index.js'
import { useCustomerCtx } from '$lib/ctx/customer/index.svelte.js'
import { Query } from '$lib/api/executor.js'
import { getFormattedMonthDayYear } from '$lib/utils/dates.js'
Expand Down
66 changes: 66 additions & 0 deletions src/lib/ui/core/Notifications/Notification.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
import { createEventDispatcher, type Snippet } from 'svelte'
import Button from '$ui/core/Button/index.js'
import Svg from '$ui/core/Svg/index.js'
import { cn } from '$ui/utils/index.js'

type Props = {
icon: 'info' | 'checkmark-circle' | 'warning' | 'error'
message: string
content?: string | Snippet
action?: { label: string; onClick: (event: MouseEvent) => void }
class?: string
}

const { icon, message, content, action, class: className }: Props = $props()

const dispatch = createEventDispatcher()

const ICONS = {
info: { class: 'fill-waterloo' },
'checkmark-circle': { class: 'fill-green' },
warning: { class: 'fill-orange', h: 14 },
error: { class: 'fill-red' },
}
</script>

<section
role="alert"
class={cn(
'flex w-[460px] max-w-full gap-4 rounded-lg border bg-white pl-6 pr-2.5 pt-5 shadow',
content && !action ? 'pb-6' : 'pb-5',
className,
)}
>
<figure class="flex h-6 w-4 center">
<Svg id={icon} {...ICONS[icon]} />
</figure>

<div class="flex-1 items-start gap-2 column">
<h4 class="text-base font-medium text-rhino">{message}</h4>

{#if content}
<p class="text-base text-fiord">
{#if typeof content === 'function'}
{@render content()}
{:else}
{content}
{/if}
</p>
{/if}

{#if action}
<Button variant="fill" class="mt-1" onclick={action.onClick}>
{action.label}
</Button>
{/if}
</div>

<Button
aria-label="Close notification"
icon="close"
iconSize={10}
class="-ml-2 -mt-2.5 flex size-5 rounded !fill-waterloo center hover:bg-porcelain xs:size-8"
onclick={() => dispatch('closeToast')}
/>
</section>
15 changes: 5 additions & 10 deletions src/lib/ui/core/Notifications/Notifications.svelte
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
<script lang="ts">
import { BROWSER } from 'esm-env'
import { Toaster } from 'svelte-sonner'
import { useDeviceCtx } from '$lib/ctx/device/index.svelte.js'

const { device } = useDeviceCtx()
</script>

{#if BROWSER}
<Toaster
position="bottom-left"
toastOptions={{
unstyled: true,
classes: {
toast: 'border rounded shadow bg-white row p-4 gap-2',
title: 'text-black font-medium',
description: 'text-waterloo',
success: 'fill-green',
},
}}
position={device.$.isMobile ? 'top-center' : 'bottom-left'}
toastOptions={{ unstyled: true }}
></Toaster>
{/if}
32 changes: 31 additions & 1 deletion src/lib/ui/core/Notifications/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
import type { ComponentProps, ComponentType } from 'svelte'
import { toast } from 'svelte-sonner'
import Component from './Notification.svelte'

export { default } from './Notifications.svelte'
export { toast as notifcation } from 'svelte-sonner'

type TOptions = Omit<ComponentProps<Component>, 'icon' | 'message'> & { duration?: number }
type TIcon = ComponentProps<Component>['icon']

function createToast(icon: TIcon, message: string, { duration = 5000, ...options }: TOptions = {}) {
return toast.custom(Component as unknown as ComponentType, {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do this because of the different types of components in Svelte 4 and Svelte 5

Copy link
Contributor Author

@serafim-san serafim-san Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error I encountered when trying to pass a component without type conversion:

Argument of type '__sveltets_2_IsomorphicComponent<Props, { closeToast: CustomEvent<any>; } & { [evt: string]: CustomEvent<any>; }, {}, {}, "">' is not assignable to parameter of type 'ComponentType'.
  Type '__sveltets_2_IsomorphicComponent<Props, { closeToast: CustomEvent<any>; } & { [evt: string]: CustomEvent<any>; }, {}, {}, "">' is not assignable to type 'new (options: ComponentConstructorOptions<Record<string, any>>) => SvelteComponent<Record<string, any>, any, any>'.
    Types of parameters 'options' and 'options' are incompatible.
      Type 'ComponentConstructorOptions<Record<string, any>>' is not assignable to type 'ComponentConstructorOptions<Props>'.
        Type 'Record<string, any>' is missing the following properties from type 'Props': icon, messagets(2345)

duration,
componentProps: { ...options, icon, message },
})
}

const createNotificationType =
(icon: TIcon) =>
(...rest: [message: string, options?: TOptions]) =>
createToast(icon, ...rest)

const notification: Record<
'info' | 'error' | 'warning' | 'success',
(message: string, options?: TOptions) => string | number
> = {
info: createNotificationType('info'),
error: createNotificationType('error'),
warning: createNotificationType('warning'),
success: createNotificationType('checkmark-circle'),
}

export { notification }
85 changes: 76 additions & 9 deletions src/stories/Design System - Core UI/Notifications/index.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,88 @@
<script>
import Button from '$ui/core/Button/index'
import { notifcation } from '$ui/core/Notifications/index'
import Button from '$ui/core/Button'
import { notification } from '$ui/core/Notifications'
import Notification from '$ui/core/Notifications/Notification.svelte'
</script>

<div class="flex flex-col justify-center divide-y p-6">
<div class="flex gap-4">
<Button variant="border" onclick={() => notifcation('Event has been created')}>Info</Button>
{#snippet content()}
We will check you insight and <a href="about:blank" target="_blank" class="text-green">
publish it very soon
</a>
{/snippet}

<div class="gap-4 p-3 column">
<Notification icon="info" message="Info notification"></Notification>
<Notification icon="checkmark-circle" message="Success notification"></Notification>
<Notification icon="warning" message="Warning notification"></Notification>
<Notification icon="error" message="Error notification"></Notification>

<Notification
icon="checkmark-circle"
message="Long message: Lorem ipsum dolor, sit amet consectetur adipisicing elit. Tenetur, recusandae doloribus! Iure praesentium"
></Notification>
</div>

<div class="flex-coljustify-center flex divide-y p-6">
<div class="flex flex-wrap gap-4">
<Button
variant="border"
onclick={() =>
notifcation.success('Event has been created', {
description: 'This is a description',
})}>Success</Button
notification.info('Thanks for your thoughts', {
content,
})}
>
Info
</Button>

<Button
variant="border"
onclick={() =>
notification.info('Informational', {
content: 'We will check your insight and publish it very soon',
action: {
label: 'Button',
onClick: () => console.log('test'),
},
})}
>
Info with button
</Button>

<Button
variant="border"
onclick={() =>
notification.info('Notification channel settings is changed', {
content,
action: {
label: 'Undo',
onClick: () => console.log('test'),
},
})}
>
Info with button
</Button>

<Button
variant="border"
onclick={() =>
notification.success('Event has been created', {
content: 'We will check your insight and publish it very soon.',
})}
>Success
</Button>

<Button
variant="border"
onclick={() =>
notification.warning('Warning', {
content:
'To activate your SanR NFT subscription, you will need to cancel your existing Sanbase Pro subscription first.',
className: 'bg-porcelain',
})}
>Warning
</Button>

<Button variant="border" onclick={() => notifcation.error('Event has been created')}
<Button variant="border" onclick={() => notification.error('Event has been created')}
>Error</Button
>
</div>
Expand Down