diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 38798eadd..357b5ff55 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -3,15 +3,14 @@ import googleAnalytics from '@analytics/google-analytics'; import { get } from 'svelte/store'; import { page } from '$app/stores'; import { user } from '$lib/stores/user'; -import { growthEndpoint, Mode } from '$lib/constants'; +import { AppwriteException } from '@aw-labs/appwrite-console'; +import { ENV, MODE, VARS } from '$lib/system'; -const isDevelopment = - import.meta.env.DEV || import.meta.env?.VITE_VERCEL_ENV?.toString() === 'preview'; const analytics = Analytics({ app: 'appwrite', plugins: [ googleAnalytics({ - measurementIds: [import.meta.env.VITE_GA_PROJECT?.toString() || 'G-R4YJ9JN8L4'] + measurementIds: [VARS.GOOGLE_ANALYTICS ?? 'G-R4YJ9JN8L4'] }) ] }); @@ -21,9 +20,17 @@ export function trackEvent(name: string, data: object = null): void { return; } - const path = get(page).route.id; + const currentPage = get(page); + const path = currentPage.route.id; - if (isDevelopment) { + if (currentPage.params?.project) { + data = { + ...data, + project: currentPage.params.project + }; + } + + if (ENV.DEV || ENV.PREVIEW) { console.debug(`[Analytics] Event ${name} ${path}`, data); } else { analytics.track(name, { ...data, path }); @@ -31,12 +38,21 @@ export function trackEvent(name: string, data: object = null): void { } } +export function trackError(exception: Error, event: Submit): void { + if (exception instanceof AppwriteException && exception.type) { + trackEvent(Submit.Error, { + type: exception.type, + form: event + }); + } +} + export function trackPageView(path: string) { if (!isTrackingAllowed()) { return; } - if (isDevelopment) { + if (ENV.DEV || ENV.PREVIEW) { console.debug(`[Analytics] Pageview ${path}`); } else { analytics.page({ @@ -46,14 +62,14 @@ export function trackPageView(path: string) { } function sendEventToGrowth(event: string, path: string, data: object = null): void { - if (!growthEndpoint) return; + if (!VARS.GROWTH_ENDPOINT) return; const userStore = get(user); let email: string, name: string; if (userStore) { email = userStore.email; name = userStore.name; } - fetch(`${growthEndpoint}/analytics`, { + fetch(`${VARS.GROWTH_ENDPOINT}/analytics`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -62,7 +78,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo action: event, label: event, url: window.location.origin + path, - account: import.meta.env.VITE_CONSOLE_MODE?.toString() || Mode.SELF_HOSTED, + account: MODE, data: { email, name, @@ -73,6 +89,9 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo } function isTrackingAllowed() { + if (ENV.TEST) { + return; + } if (window.navigator?.doNotTrack) { if (navigator.doNotTrack === '1' || navigator.doNotTrack === 'yes') { return false; @@ -83,3 +102,109 @@ function isTrackingAllowed() { return true; } } + +export enum Submit { + Error = 'submit_error', + AccountCreate = 'submit_account_create', + AccountLogin = 'submit_account_login', + AccountLogout = 'submit_account_logout', + AccountRecover = 'submit_account_recover', + AccountUpdateName = 'submit_account_update_name', + AccountUpdateEmail = 'submit_account_update_email', + AccountUpdatePassword = 'submit_account_update_password', + AccountDelete = 'submit_account_delete', + AccountDeleteSession = 'submit_account_delete_session', + AccountDeleteAllSessions = 'submit_account_delete_all_sessions', + UserCreate = 'submit_user_create', + UserDelete = 'submit_user_delete', + UserUpdateEmail = 'submit_user_update_email', + UserUpdateName = 'submit_user_update_name', + UserUpdatePassword = 'submit_user_update_password', + UserUpdatePhone = 'submit_user_update_phone', + UserUpdatePreferences = 'submit_user_update_preferences', + UserUpdateStatus = 'submit_user_update_status', + UserUpdateVerificationEmail = 'submit_user_update_verification_email', + UserUpdateVerificationPhone = 'submit_user_update_verification_phone', + OrganizationCreate = 'submit_organization_create', + OrganizationDelete = 'submit_organization_delete', + OrganizationUpdateName = 'submit_organization_update_name', + ProjectCreate = 'submit_project_create', + ProjectDelete = 'submit_project_delete', + ProjectUpdateName = 'submit_project_update_name', + ProjectService = 'submit_project_service', + MemberCreate = 'submit_member_create', + MemberDelete = 'submit_member_delete', + MembershipUpdateStatus = 'submit_membership_update_status', + ProviderUpdate = 'submit_provider_update', + TeamCreate = 'submit_team_create', + TeamDelete = 'submit_team_delete', + TeamUpdateName = 'submit_team_update_name', + AuthLimitUpdate = 'submit_auth_limit_update', + AuthStatusUpdate = 'submit_auth_status_update', + SessionsLengthUpdate = 'submit_sessions_length_update', + SessionsLimitUpdate = 'submit_sessions_limit_update', + SessionDelete = 'submit_session_delete', + SessionDeleteAll = 'submit_session_delete_all', + DatabaseCreate = 'submit_database_create', + DatabaseDelete = 'submit_database_delete', + DatabaseUpdateName = 'submit_database_update_name', + AttributeCreate = 'submit_attribute_create', + AttributeDelete = 'submit_attribute_delete', + DocumentCreate = 'submit_document_create', + DocumentDelete = 'submit_document_delete', + DocumentUpdate = 'submit_document_update', + DocumentUpdatePermissions = 'submit_document_update_permissions', + IndexCreate = 'submit_index_create', + IndexDelete = 'submit_index_delete', + CollectionCreate = 'submit_collection_create', + CollectionDelete = 'submit_collection_delete', + CollectionUpdateName = 'submit_collection_update_name', + CollectionUpdatePermissions = 'submit_collection_update_permissions', + CollectionUpdateSecurity = 'submit_collection_update_security', + CollectionUpdateEnabled = 'submit_collection_update_enabled', + FunctionCreate = 'submit_function_create', + FunctionDelete = 'submit_function_delete', + FunctionUpdateName = 'submit_function_update_name', + FunctionUpdatePermissions = 'submit_function_update_permissions', + FunctionUpdateSchedule = 'submit_function_update_schedule', + FunctionUpdateTimeout = 'submit_function_update_timeout', + FunctionUpdateEvents = 'submit_function_update_events', + DeploymentCreate = 'submit_deployment_create', + DeploymentDelete = 'submit_deployment_delete', + DeploymentUpdate = 'submit_deployment_update', + ExecutionCreate = 'submit_execution_create', + VariableCreate = 'submit_variable_create', + VariableDelete = 'submit_variable_delete', + VariableUpdate = 'submit_variable_update', + KeyCreate = 'submit_key_create', + KeyDelete = 'submit_key_delete', + KeyUpdateName = 'submit_key_update_name', + KeyUpdateScopes = 'submit_key_update_scopes', + KeyUpdateExpire = 'submit_key_update_expire', + PlatformCreate = 'submit_platform_create', + PlatformDelete = 'submit_platform_delete', + PlatformUpdate = 'submit_platform_update', + DomainCreate = 'submit_domain_create', + DomainDelete = 'submit_domain_delete', + DomainUpdateVerification = 'submit_domain_update_verification', + WebhookCreate = 'submit_webhook_create', + WebhookDelete = 'submit_webhook_delete', + WebhookUpdateSignature = 'submit_webhook_update_signature', + WebhookUpdateUrl = 'submit_webhook_update_url', + WebhookUpdateEvents = 'submit_webhook_update_events', + WebhookUpdateName = 'submit_webhook_update_name', + WebhookUpdateSecurity = 'submit_webhook_update_security', + BucketCreate = 'submit_bucket_create', + BucketDelete = 'submit_bucket_delete', + BucketUpdateEnabled = 'submit_bucket_update_enabled', + BucketUpdateName = 'submit_bucket_update_name', + BucketUpdatePermissions = 'submit_bucket_update_permissions', + BucketUpdateSecurity = 'submit_bucket_update_security', + BucketUpdateFileSecurity = 'submit_bucket_update_file_security', + BucketUpdateSize = 'submit_bucket_update_size', + BucketUpdateCompression = 'submit_bucket_update_compression', + BucketUpdateExtensions = 'submit_bucket_update_extensions', + FileCreate = 'submit_file_create', + FileDelete = 'submit_file_delete', + FileUpdatePermissions = 'submit_file_update_permissions' +} diff --git a/src/lib/components/copy.svelte b/src/lib/components/copy.svelte index 8271c6ebc..c6cc8fe7d 100644 --- a/src/lib/components/copy.svelte +++ b/src/lib/components/copy.svelte @@ -7,6 +7,7 @@ export let value: string; export let event: string = null; + export let eventContext = 'click_id_tag'; export let appendTo: Parameters['1']['appendTo'] = undefined; let content = 'Click to copy'; @@ -24,7 +25,7 @@ } if (event) { - trackEvent('click_id_tag', { + trackEvent(eventContext, { name: event }); } diff --git a/src/lib/components/copyInput.svelte b/src/lib/components/copyInput.svelte index b06f1ae36..b19d9b002 100644 --- a/src/lib/components/copyInput.svelte +++ b/src/lib/components/copyInput.svelte @@ -31,7 +31,7 @@
- + diff --git a/src/lib/constants.ts b/src/lib/constants.ts index d8015a1f4..0a19c9208 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -2,13 +2,6 @@ export const PAGE_LIMIT = 12; // default page limit export const CARD_LIMIT = 6; // default card limit export const INTERVAL = 5 * 60000; // default interval to check for feedback -export enum Mode { - CLOUD = 'cloud', - SELF_HOSTED = 'self-hosted' -} - -export const growthEndpoint = import.meta.env.VITE_APPWRITE_GROWTH_ENDPOINT; - export enum Dependencies { ORGANIZATION = 'dependency:organization', PROJECT = 'dependency:project', @@ -251,7 +244,7 @@ export const eventServices: Array = [ resources: [ { name: 'recovery', actions: [{ name: 'create' }, { name: 'delete' }] }, { name: 'sessions', actions: [{ name: 'create' }, { name: 'delete' }] }, - { name: 'verifications', actions: [{ name: 'create' }, { name: 'delete' }] } + { name: 'verification', actions: [{ name: 'create' }, { name: 'delete' }] } ], actions: [ { name: 'create' }, diff --git a/src/lib/elements/forms/index.ts b/src/lib/elements/forms/index.ts index dd02f6964..d60fb5442 100644 --- a/src/lib/elements/forms/index.ts +++ b/src/lib/elements/forms/index.ts @@ -23,3 +23,4 @@ export { default as InputURL } from './inputURL.svelte'; export { default as InputId } from './inputId.svelte'; export { default as InputSecret } from './inputSecret.svelte'; export { default as Helper } from './helper.svelte'; +export { default as Label } from './label.svelte'; diff --git a/src/lib/elements/forms/inputCheckbox.svelte b/src/lib/elements/forms/inputCheckbox.svelte index 7e2f89ac9..b8c601635 100644 --- a/src/lib/elements/forms/inputCheckbox.svelte +++ b/src/lib/elements/forms/inputCheckbox.svelte @@ -1,5 +1,5 @@ -
import { onMount } from 'svelte'; - import { FormItem, Helper } from '.'; + import { FormItem, Helper, Label } from '.'; export let label: string; export let showLabel = true; @@ -48,7 +48,10 @@ - + +
import { onMount } from 'svelte'; - import { FormItem, Helper } from '.'; + import { FormItem, Helper, Label } from '.'; export let label: string; export let showLabel = true; @@ -39,12 +39,9 @@ -
import { onMount } from 'svelte'; - import { FormItem, Helper } from '.'; + import { FormItem, Helper, Label } from '.'; export let label: string; export let showLabel = true; @@ -47,7 +47,10 @@ - + +
import { onMount } from 'svelte'; - import { FormItem, Helper } from '.'; + import { FormItem, Helper, Label } from '.'; export let label: string; export let showLabel = true; @@ -41,7 +41,10 @@ - + +
import { Trim } from '$lib/components'; import { humanFileSize } from '$lib/helpers/sizeConvertion'; + import { Helper } from '.'; export let label: string = null; export let files: FileList; export let list = new DataTransfer(); export let allowedFileExtensions: string[] = []; export let maxSize: number = null; + export let required = false; + export let error: string = null; let input: HTMLInputElement; let hovering = false; @@ -29,6 +32,20 @@ hovering = true; } + const handleInvalid = (event: Event) => { + event.preventDefault(); + + if (input.validity.valueMissing) { + error = 'This field is required'; + return; + } + error = input.validationMessage; + }; + + $: if (files) { + error = null; + } + $: fileArray = files?.length ? Array.from(files) : []; @@ -37,7 +54,9 @@ bind:this={input} accept={allowedFileExtensions.map((n) => `.${n}`).join(',')} type="file" - style="display: none" /> + style="display: none" + {required} + on:invalid={handleInvalid} />
{#if label} @@ -117,4 +136,7 @@ {/if}
+ {#if error} + {error} + {/if}
diff --git a/src/lib/elements/forms/inputNumber.svelte b/src/lib/elements/forms/inputNumber.svelte index 7f3b9ee48..9e43b3fb3 100644 --- a/src/lib/elements/forms/inputNumber.svelte +++ b/src/lib/elements/forms/inputNumber.svelte @@ -1,6 +1,6 @@ -
import { onMount } from 'svelte'; - import { FormItem, Helper } from '.'; + import { FormItem, Helper, Label } from '.'; export let id: string; export let label: string; @@ -47,7 +47,10 @@ - + +
{#if showInPlainText} import { onMount } from 'svelte'; - import { FormItem, Helper } from '.'; + import { FormItem, Helper, Label } from '.'; export let label: string; export let showLabel = true; @@ -46,7 +46,9 @@ - +
- import { FormItem, Helper } from '.'; + import { FormItem, Helper, Label } from '.'; export let label: string; export let showLabel = true; @@ -40,7 +40,9 @@ bind:group bind:this={element} on:invalid={handleInvalid} /> - +
{#if error} {error} diff --git a/src/lib/elements/forms/inputSecret.svelte b/src/lib/elements/forms/inputSecret.svelte index f7244bf75..c1932133d 100644 --- a/src/lib/elements/forms/inputSecret.svelte +++ b/src/lib/elements/forms/inputSecret.svelte @@ -1,5 +1,5 @@ - + +
{#if showInPlainText}
- import { FormItem, Helper } from '.'; + import { FormItem, Helper, Label } from '.'; export let id: string; export let label: string; @@ -27,18 +27,23 @@ error = element.validationMessage; }; + $: if (element && required && !value) { + element.setCustomValidity('This field is required'); + } + + $: if (element && required && value) { + element.setCustomValidity(''); + } + $: if (value) { error = null; } -
+ {#if maxlength} + + {value?.length ?? 0} + + {maxlength} + + {/if}
{#if error} {error} diff --git a/src/lib/elements/forms/inputTextarea.svelte b/src/lib/elements/forms/inputTextarea.svelte index 8c828baf9..7285b6f24 100644 --- a/src/lib/elements/forms/inputTextarea.svelte +++ b/src/lib/elements/forms/inputTextarea.svelte @@ -1,6 +1,6 @@ - + +