mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
@@ -7,20 +7,29 @@
|
||||
export let scrollable = false;
|
||||
export let childStart = false;
|
||||
export let noArrow = false;
|
||||
export let width: string = null;
|
||||
</script>
|
||||
|
||||
<Drop bind:show {placement} {childStart} {noArrow}>
|
||||
<slot />
|
||||
<svelte:fragment slot="list">
|
||||
<div class="drop is-no-arrow" style="position: revert">
|
||||
<section
|
||||
class:u-overflow-y-auto={scrollable}
|
||||
class:u-max-height-200={scrollable}
|
||||
class="drop-section ">
|
||||
<ul class="drop-list">
|
||||
<slot name="list" />
|
||||
</ul>
|
||||
</section>
|
||||
<div
|
||||
class="drop is-no-arrow"
|
||||
style={`${
|
||||
width
|
||||
? `--drop-width-size-desktop:${width}rem; position:relative`
|
||||
: ' position:relative'
|
||||
}`}>
|
||||
{#if $$slots.list}
|
||||
<section
|
||||
class:u-overflow-y-auto={scrollable}
|
||||
class:u-max-height-200={scrollable}
|
||||
class="drop-section ">
|
||||
<ul class="drop-list">
|
||||
<slot name="list" />
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
<slot name="other" />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
|
||||
export let value: number = null;
|
||||
</script>
|
||||
|
||||
<fieldset class="u-margin-block-start-8">
|
||||
<legend class="label is-required u-margin-0 u-line-height-1-5 u-bold">
|
||||
<slot />
|
||||
</legend>
|
||||
<ul class="u-flex u-main-space-between u-margin-block-start-16">
|
||||
{#each Array(10) as _, i}
|
||||
<li on:keyup={clickOnEnter} on:click={() => (value = i)}>
|
||||
<input
|
||||
type="radio"
|
||||
name="recommend"
|
||||
class="radio-button"
|
||||
data-text={i}
|
||||
checked={value === i} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="u-flex u-main-space-between u-margin-block-start-8">
|
||||
<span>Not at all likely</span>
|
||||
<span>Extremely likely</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Form,
|
||||
FormList,
|
||||
InputTextarea,
|
||||
Button,
|
||||
InputText,
|
||||
InputEmail
|
||||
} from '$lib/elements/forms';
|
||||
import { feedback } from '$lib/stores/app';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let show = false;
|
||||
|
||||
let message: string;
|
||||
let name: string;
|
||||
let email: string;
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await feedback.submitFeedback(
|
||||
undefined,
|
||||
email,
|
||||
message,
|
||||
['general'],
|
||||
[{ firstname: name }]
|
||||
);
|
||||
show = false;
|
||||
} catch (error) {
|
||||
show = false;
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="drop-section">
|
||||
<header class="u-flex u-main-space-between u-gap-16">
|
||||
<h4 class="body-text-1">How can we improve?</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="x-button u-margin-inline-start-auto"
|
||||
aria-label="Close Modal"
|
||||
title="Close Modal"
|
||||
on:click={() => (show = false)}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
<div class="u-margin-block-start-8 u-line-height-1-5">
|
||||
Your feedback is important to us. Please be honest and tell us what you think.
|
||||
</div>
|
||||
|
||||
<Form on:submit={handleSubmit}>
|
||||
<FormList>
|
||||
<InputText
|
||||
label="name"
|
||||
id="name"
|
||||
bind:value={name}
|
||||
placeholder="Enter name"
|
||||
showLabel={false} />
|
||||
<InputEmail
|
||||
label="email"
|
||||
id="email"
|
||||
bind:value={email}
|
||||
placeholder="Enter email"
|
||||
showLabel={false} />
|
||||
<InputTextarea
|
||||
id="feedback"
|
||||
placeholder="Your message here"
|
||||
showLabel={false}
|
||||
label="Feedback"
|
||||
required
|
||||
bind:value={message} />
|
||||
</FormList>
|
||||
|
||||
<div class="u-flex u-main-end u-gap-16 u-margin-block-start-24">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary submit>Submit</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</section>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Form,
|
||||
FormList,
|
||||
InputTextarea,
|
||||
Button,
|
||||
InputText,
|
||||
InputEmail
|
||||
} from '$lib/elements/forms';
|
||||
import { feedback } from '$lib/stores/app';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import Evaluation from './evaluation.svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
let value: number = null;
|
||||
let message: string;
|
||||
let email: string;
|
||||
let name: string;
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await feedback.submitFeedback(undefined, email, message, ['npm'], [{ value, name }]);
|
||||
console.log(value, message);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="drop-section">
|
||||
<header class="u-flex u-main-space-between u-gap-16">
|
||||
<h4 class="body-text-1">How are we doing?</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="x-button u-margin-inline-start-auto"
|
||||
aria-label="Close Modal"
|
||||
title="Close Modal"
|
||||
on:click={() => (show = false)}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
<div class="u-margin-block-start-8 u-line-height-1-5">
|
||||
At Appwrite, we value the feedback of our users. That means you! We'd love to hear what you
|
||||
think.
|
||||
</div>
|
||||
|
||||
<Form on:submit={handleSubmit}>
|
||||
<Evaluation bind:value>
|
||||
How likely are you to recommend Appwrite to a friend or colleague?
|
||||
</Evaluation>
|
||||
{#if value}
|
||||
<FormList>
|
||||
<InputTextarea
|
||||
id="feedback"
|
||||
placeholder="Your message here"
|
||||
label="Feedback"
|
||||
required
|
||||
bind:value={message}
|
||||
showLabel={false} />
|
||||
<InputText
|
||||
label="name"
|
||||
id="name"
|
||||
bind:value={name}
|
||||
placeholder="Enter name"
|
||||
showLabel={false} />
|
||||
<InputEmail
|
||||
label="email"
|
||||
id="email"
|
||||
bind:value={email}
|
||||
placeholder="Enter email"
|
||||
showLabel={false} />
|
||||
</FormList>
|
||||
{/if}
|
||||
<div class="u-flex u-main-end u-gap-16 u-margin-block-start-24">
|
||||
<Button text on:click={() => (show = false)}>No thanks</Button>
|
||||
<Button disabled={!value} secondary submit>Submit</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</section>
|
||||
@@ -31,6 +31,9 @@ export { default as Box } from './box.svelte';
|
||||
export { default as Search } from './search.svelte';
|
||||
export { default as SearchQuery } from './searchQuery.svelte';
|
||||
export { default as GridItem1 } from './gridItem1.svelte';
|
||||
export { default as FeedbackGeneral } from './feedbackGeneral.svelte';
|
||||
export { default as FeedbackNPS } from './feedbackNPS.svelte';
|
||||
export { default as Evaluation } from './evaluation.svelte';
|
||||
export { default as Steps } from './steps.svelte';
|
||||
export { default as Step } from './step.svelte';
|
||||
export { default as Code } from './code.svelte';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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 Dependencies {
|
||||
ORGANIZATION = 'dependency:organization',
|
||||
PROJECT = 'dependency:project',
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { AvatarInitials, DropListItem, DropListLink } from '$lib/components';
|
||||
import { app } from '$lib/stores/app';
|
||||
import {
|
||||
AvatarInitials,
|
||||
DropList,
|
||||
DropListItem,
|
||||
DropListLink,
|
||||
FeedbackGeneral
|
||||
} from '$lib/components';
|
||||
import { app, feedback } from '$lib/stores/app';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { organizationList, organization, newOrgModal } from '$lib/stores/organization';
|
||||
import AppwriteLogo from '$lib/images/appwrite-gray-light.svg';
|
||||
import LightMode from '$lib/images/mode/light-mode.svg';
|
||||
import DarkMode from '$lib/images/mode/dark-mode.svg';
|
||||
import SystemMode from '$lib/images/mode/system-mode.svg';
|
||||
import { FeedbackNPS } from '$lib/components';
|
||||
|
||||
let showFeedback = false;
|
||||
import { slide } from 'svelte/transition';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let showDropdown = false;
|
||||
let droplistElement: HTMLDivElement;
|
||||
|
||||
const onBlur = (event: MouseEvent) => {
|
||||
function toggleFeedback() {
|
||||
showFeedback = !showFeedback;
|
||||
if ($feedback.notification) {
|
||||
feedback.toggleNotification();
|
||||
feedback.addVisualization();
|
||||
}
|
||||
}
|
||||
|
||||
function onBlur(event: MouseEvent) {
|
||||
if (
|
||||
showDropdown &&
|
||||
!(event.target === droplistElement || droplistElement.contains(event.target as Node))
|
||||
) {
|
||||
showDropdown = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:click={onBlur} />
|
||||
@@ -38,7 +55,23 @@
|
||||
|
||||
<div class="main-header-end">
|
||||
<nav class="u-flex is-only-desktop">
|
||||
<button class="button is-small is-text"><span class="text">Feedback</span></button>
|
||||
{#if $feedback.notification}
|
||||
<div class="u-flex u-cross-center">
|
||||
<div class="pulse-notification" />
|
||||
</div>
|
||||
{/if}
|
||||
<DropList width="28" bind:show={showFeedback} scrollable={true}>
|
||||
<button class="button is-small is-text" on:click={toggleFeedback}>
|
||||
<span class="text">Feedback</span>
|
||||
</button>
|
||||
<svelte:fragment slot="other">
|
||||
{#if $feedback.notification}
|
||||
<FeedbackNPS bind:show={showFeedback} />
|
||||
{:else}
|
||||
<FeedbackGeneral bind:show={showFeedback} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
<a
|
||||
href="https://appwrite.io/support"
|
||||
target="_blank"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { DropList } from '$lib/components';
|
||||
|
||||
export let show = false;
|
||||
</script>
|
||||
|
||||
<DropList bind:show>
|
||||
<button id="button" on:click={() => (show = !show)}>test</button>
|
||||
|
||||
<svelte:fragment slot="list"><li id="list">list</li></svelte:fragment>
|
||||
<svelte:fragment slot="other"><span id="other">other</span></svelte:fragment>
|
||||
</DropList>
|
||||
@@ -5,11 +5,81 @@ export type AppStore = {
|
||||
theme: 'light' | 'dark' | 'auto';
|
||||
themeInUse: 'light' | 'dark';
|
||||
};
|
||||
|
||||
export type Feedback = {
|
||||
elapsed: number;
|
||||
visualized: number;
|
||||
notification: boolean;
|
||||
type: 'nps' | 'general';
|
||||
};
|
||||
|
||||
export const app = writable<AppStore>({
|
||||
theme: 'auto',
|
||||
themeInUse: 'light'
|
||||
});
|
||||
|
||||
function createFeedbackStore() {
|
||||
const { subscribe, update } = writable<Feedback>({
|
||||
elapsed: browser ? parseInt(localStorage.getItem('feedbackElapsed')) : 0,
|
||||
visualized: browser ? parseInt(localStorage.getItem('feedbackVisualized')) : 0,
|
||||
notification: false,
|
||||
type: 'nps'
|
||||
});
|
||||
return {
|
||||
subscribe,
|
||||
update,
|
||||
toggleNotification: () =>
|
||||
update((feedback) => {
|
||||
feedback.notification = !feedback.notification;
|
||||
return feedback;
|
||||
}),
|
||||
switchType: () =>
|
||||
update((feedback) => {
|
||||
feedback.type = feedback.type === 'nps' ? 'general' : 'nps';
|
||||
return feedback;
|
||||
}),
|
||||
addVisualization: () =>
|
||||
update((feedback) => {
|
||||
feedback.visualized += 1;
|
||||
localStorage.setItem('feedbackVisualized', feedback.visualized.toString());
|
||||
return feedback;
|
||||
}),
|
||||
|
||||
increaseElapsed: (time: number) => {
|
||||
update((feedback) => {
|
||||
feedback.elapsed += time;
|
||||
localStorage.setItem('feedbackElapsed', feedback.elapsed.toString());
|
||||
return feedback;
|
||||
});
|
||||
},
|
||||
submitFeedback: async (
|
||||
subject?: string,
|
||||
email?: string,
|
||||
message?: string,
|
||||
tags?: string[],
|
||||
customFields?: Record<string, unknown>[]
|
||||
) => {
|
||||
const response = await fetch('https://growth.appwrite.io/v1/feedback', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
subject,
|
||||
email,
|
||||
message,
|
||||
tags,
|
||||
customFields
|
||||
})
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
throw new Error('Failed to submit feedback');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
export const feedback = createFeedbackStore();
|
||||
|
||||
if (browser) {
|
||||
app.update((n) => ({
|
||||
...n,
|
||||
|
||||
@@ -11,11 +11,28 @@
|
||||
import { log } from '$lib/stores/logs';
|
||||
import { onMount } from 'svelte';
|
||||
import { loading } from '../store';
|
||||
import { feedback } from '$lib/stores/app';
|
||||
import { INTERVAL } from '$lib/constants';
|
||||
|
||||
onMount(() => {
|
||||
loading.set(false);
|
||||
|
||||
setInterval(() => {
|
||||
checkForFeedback(INTERVAL);
|
||||
}, INTERVAL);
|
||||
});
|
||||
|
||||
function checkForFeedback(interval: number) {
|
||||
const minutes = interval / 60000;
|
||||
feedback.increaseElapsed(minutes);
|
||||
const hours = Math.floor($feedback.elapsed / 60);
|
||||
if (hours >= 1 && hours < 10 && $feedback.visualized < 1) {
|
||||
feedback.toggleNotification();
|
||||
} else if (hours >= $feedback.visualized * 10) {
|
||||
feedback.toggleNotification();
|
||||
}
|
||||
}
|
||||
|
||||
beforeNavigate(() => {
|
||||
$log.show = false;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import { DropList } from '../../../src/lib/components';
|
||||
import DropList from '../../../src/lib/mock/drop.test.svelte';
|
||||
|
||||
const data = {
|
||||
show: true
|
||||
|
||||
Reference in New Issue
Block a user