mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'feat-pink-v2' into project-wizard-animation
This commit is contained in:
+3
-3
@@ -19,11 +19,11 @@
|
||||
"e2e:ui": "playwright test tests/e2e --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@ae1eb53",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08ec153",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f29290",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f0102c9f",
|
||||
"@appwrite.io/pink-legacy": "^1.0.1",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@35f29290",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@f99d781",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^8.38.0",
|
||||
"@stripe/stripe-js": "^3.5.0",
|
||||
|
||||
Generated
+19
-19
@@ -9,20 +9,20 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@ae1eb53
|
||||
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@ae1eb53
|
||||
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08ec153
|
||||
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08ec153
|
||||
'@appwrite.io/pink-icons':
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
'@appwrite.io/pink-icons-svelte':
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f29290
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f29290(svelte@4.2.19)
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f0102c9f
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f0102c9f(svelte@4.2.19)
|
||||
'@appwrite.io/pink-legacy':
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
'@appwrite.io/pink-svelte':
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@35f29290
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@35f29290(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@f99d781
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@f99d781(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
@@ -211,18 +211,18 @@ packages:
|
||||
'@analytics/type-utils@0.6.2':
|
||||
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@ae1eb53':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@ae1eb53}
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08ec153':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08ec153}
|
||||
version: 1.2.1
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f29290':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f29290}
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f0102c9f':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f0102c9f}
|
||||
version: 1.0.0-next.7
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f2929064b9d126a377b6a3e85f59198c5901a2':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f2929064b9d126a377b6a3e85f59198c5901a2}
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f99d781924ba95e7ed83708ca8e4c26f80293c3c':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f99d781924ba95e7ed83708ca8e4c26f80293c3c}
|
||||
version: 1.0.0-next.7
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -236,8 +236,8 @@ packages:
|
||||
'@appwrite.io/pink-legacy@1.0.1':
|
||||
resolution: {integrity: sha512-e5IZx2drqSxgl2WXvmr0eLfg/WiOCzuqQE8oksD+9GoFBkcL9HWvXul5GjFni3RBAN9XFRL3nUExGk4T0H8sFw==}
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@35f29290':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@35f29290}
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@f99d781':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@f99d781}
|
||||
version: 1.0.0-next.85
|
||||
peerDependencies:
|
||||
react-dom: ^18.0.0
|
||||
@@ -4097,13 +4097,13 @@ snapshots:
|
||||
|
||||
'@analytics/type-utils@0.6.2': {}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@ae1eb53': {}
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08ec153': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f29290(svelte@4.2.19)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f0102c9f(svelte@4.2.19)':
|
||||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f2929064b9d126a377b6a3e85f59198c5901a2(svelte@4.2.19)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f99d781924ba95e7ed83708ca8e4c26f80293c3c(svelte@4.2.19)':
|
||||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
||||
@@ -4116,9 +4116,9 @@ snapshots:
|
||||
'@appwrite.io/pink-icons': 1.0.0
|
||||
the-new-css-reset: 1.11.3
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@35f29290(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)':
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@f99d781(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)':
|
||||
dependencies:
|
||||
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@35f2929064b9d126a377b6a3e85f59198c5901a2(svelte@4.2.19)
|
||||
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@f99d781924ba95e7ed83708ca8e4c26f80293c3c(svelte@4.2.19)
|
||||
'@floating-ui/dom': 1.6.13
|
||||
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.3(svelte@4.2.19))(svelte@4.2.19)
|
||||
'@melt-ui/svelte': 0.86.3(svelte@4.2.19)
|
||||
|
||||
@@ -343,5 +343,6 @@ export enum Submit {
|
||||
SiteDisconnectRepo = 'submit_site_disconnect_repo',
|
||||
SiteActivateDeployment = 'submit_site_activate_deployment',
|
||||
RecordCreate = 'submit_dns_record_create',
|
||||
RecordUpdate = 'submit_dns_record_update',
|
||||
RecordDelete = 'submit_dns_record_delete'
|
||||
}
|
||||
|
||||
+117
-84
@@ -1,5 +1,121 @@
|
||||
{
|
||||
"backgroundColor": "rgba(255,255,255,0)",
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)",
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)",
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisLine": {
|
||||
"show": false
|
||||
},
|
||||
"axisTick": {
|
||||
"show": false
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false
|
||||
}
|
||||
},
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)",
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false
|
||||
}
|
||||
},
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)",
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--color-border-neutral)",
|
||||
"textStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-secondary)"
|
||||
},
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-border-neutral-strong)",
|
||||
"width": 1
|
||||
},
|
||||
"crossStyle": {
|
||||
"color": "var(--color-border-neutral-strong)",
|
||||
"width": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"textStyle": {},
|
||||
"line": {
|
||||
"itemStyle": {
|
||||
@@ -12,89 +128,6 @@
|
||||
"symbol": "emptyCircle",
|
||||
"smooth": true
|
||||
},
|
||||
"bar": {
|
||||
"itemStyle": {
|
||||
"barBorderColor": "#ccc"
|
||||
}
|
||||
},
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLine": {
|
||||
"show": false
|
||||
},
|
||||
"axisTick": {
|
||||
"show": false
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false
|
||||
}
|
||||
},
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false
|
||||
}
|
||||
},
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"width": 1
|
||||
},
|
||||
"crossStyle": {
|
||||
"width": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"lineStyle": {
|
||||
"width": 1
|
||||
|
||||
@@ -4,15 +4,13 @@
|
||||
import { app } from '$lib/stores/app';
|
||||
import { dailyFormat, hourlyFormat, defaultConfig } from './config';
|
||||
import base from './base.json';
|
||||
import light from './light.json';
|
||||
import dark from './dark.json';
|
||||
import type { ECharts } from 'echarts/core';
|
||||
import type { BarSeriesOption, LineSeriesOption } from 'echarts/charts';
|
||||
import type { EChartsOption } from 'echarts';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
|
||||
registerTheme('light', { ...base, ...light });
|
||||
registerTheme('dark', { ...base, ...dark });
|
||||
registerTheme('light', base);
|
||||
registerTheme('dark', base);
|
||||
|
||||
export let options: EChartsOption;
|
||||
export let series: (BarSeriesOption | LineSeriesOption)[];
|
||||
|
||||
@@ -120,10 +120,10 @@
|
||||
"color": "#444444"
|
||||
}
|
||||
},
|
||||
"backgroundColor": "#373B4D",
|
||||
"borderColor": "#373B4D",
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--color-border-neutral)",
|
||||
"textStyle": {
|
||||
"color": "#F2F2F8"
|
||||
"color": "var(--color-fgcolor-neutral-secondary)"
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
|
||||
+21
-88
@@ -1,149 +1,82 @@
|
||||
{
|
||||
"backgroundColor": "rgba(255,255,255,0)",
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"color": "#373b4d"
|
||||
},
|
||||
"subtextStyle": {
|
||||
"color": "#616b7c"
|
||||
}
|
||||
},
|
||||
"bar": {
|
||||
"itemStyle": {
|
||||
"barBorderColor": "#ccc"
|
||||
}
|
||||
},
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLabel": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["#E8E9F0"]
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["#E0E6F1"]
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#6E7079"
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["#E0E6F1"]
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"toolbox": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#999"
|
||||
},
|
||||
"emphasis": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#666"
|
||||
}
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"color": "#333"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--color-border-neutral)",
|
||||
"textStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-secondary)"
|
||||
},
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"color": "#ccc"
|
||||
"color": "var(--color-border-neutral-strong)"
|
||||
},
|
||||
"crossStyle": {
|
||||
"color": "#ccc"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"lineStyle": {
|
||||
"color": "#DAE1F5"
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#A4B1D7"
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#A4B1D7",
|
||||
"borderColor": "#A4B1D7"
|
||||
},
|
||||
"checkpointStyle": {
|
||||
"color": "#316bf3",
|
||||
"borderColor": "#fff"
|
||||
},
|
||||
"label": {
|
||||
"color": "#A4B1D7"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"color": "#FFF"
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#A4B1D7",
|
||||
"borderColor": "#A4B1D7"
|
||||
},
|
||||
"label": {
|
||||
"color": "#A4B1D7"
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualMap": {
|
||||
"color": ["#bf444c", "#d88273", "#f6efa6"]
|
||||
},
|
||||
"markPoint": {
|
||||
"label": {
|
||||
"color": "#eee"
|
||||
},
|
||||
"emphasis": {
|
||||
"label": {
|
||||
"color": "#eee"
|
||||
"color": "var(--color-border-neutral-strong)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import type { AvatarProps } from '@appwrite.io/pink-svelte/dist/avatar/Avatar.svelte';
|
||||
|
||||
export let size: AvatarProps['size'] = 'm';
|
||||
export let src: AvatarProps['src'];
|
||||
export let src: AvatarProps['src'] = undefined;
|
||||
export let alt: AvatarProps['alt'];
|
||||
</script>
|
||||
|
||||
<Avatar {alt} {src} {size} />
|
||||
<Avatar {alt} {src} {size}>
|
||||
<slot />
|
||||
</Avatar>
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
|
||||
{#if total > 2}
|
||||
<Avatar {size}>
|
||||
+{total - 2}
|
||||
<span style:font-size="10px">
|
||||
+{total - 2}
|
||||
</span>
|
||||
</Avatar>
|
||||
{/if}
|
||||
</AvatarGroup>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { Button, Form, InputCheckbox } from '$lib/elements/forms';
|
||||
import { Alert, Dialog, Layout } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let open: boolean;
|
||||
@@ -8,9 +8,13 @@
|
||||
export let action: string = 'Delete';
|
||||
export let canDelete: boolean = true;
|
||||
export let disabled: boolean = false;
|
||||
export let confirmDeletion: boolean = false;
|
||||
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
|
||||
return;
|
||||
};
|
||||
|
||||
let confirm = false;
|
||||
let checkboxId = `delete_${title.replaceAll(' ', '_').toLowerCase()}`;
|
||||
</script>
|
||||
|
||||
<Form isModal {onSubmit}>
|
||||
@@ -25,13 +29,32 @@
|
||||
{error}
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<slot />
|
||||
|
||||
<Layout.Stack gap="l">
|
||||
<slot />
|
||||
|
||||
{#if confirmDeletion}
|
||||
<InputCheckbox
|
||||
size="s"
|
||||
required
|
||||
id={checkboxId}
|
||||
bind:checked={confirm}
|
||||
label="I understand and confirm" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Layout.Stack direction="row" gap="s" justifyContent="flex-end">
|
||||
<Button text on:click={() => (open = false)}>Cancel</Button>
|
||||
{#if canDelete}
|
||||
<Button danger submit {disabled}>{action}</Button>
|
||||
{/if}
|
||||
<slot name="footer">
|
||||
<Button text on:click={() => (open = false)}>Cancel</Button>
|
||||
{#if canDelete}
|
||||
<Button
|
||||
danger
|
||||
submit
|
||||
disabled={disabled || (confirmDeletion ? !confirm : false)}
|
||||
>{action}</Button>
|
||||
{/if}
|
||||
</slot>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</Dialog>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
export let optionalText: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<Input.Text readonly {value} {label} helper={optionalText}>
|
||||
<Input.Text readonly {value} {label} helper={optionalText} required>
|
||||
<Copy {value} slot="end">
|
||||
<Layout.Stack>
|
||||
<Input.Action icon={IconDuplicate} />
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
{#if single}
|
||||
<Card.Base padding="none">
|
||||
<Empty title={`Create your first ${target}`} {description} {src}>
|
||||
<svelte:fragment slot="actions">
|
||||
<slot name="actions" slot="actions">
|
||||
<Button
|
||||
external
|
||||
{href}
|
||||
@@ -44,16 +44,14 @@
|
||||
size="s">
|
||||
Create {target}
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</slot>
|
||||
</Empty>
|
||||
</Card.Base>
|
||||
{:else}
|
||||
<Card.Button on:click on:click={track} aria-label="create">
|
||||
<Layout.Stack justifyContent="center" alignItems="center" gap="m">
|
||||
<Avatar>
|
||||
<Icon icon={IconPlus} />
|
||||
</Avatar>
|
||||
<Typography.Text variation="m-400"><slot /></Typography.Text>
|
||||
<Avatar size="s"><Icon icon={IconPlus} size="s" /></Avatar>
|
||||
<Typography.Text variation="m-400" align="center"><slot /></Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card.Button>
|
||||
{/if}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
ValidOperators
|
||||
} from './store';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { Icon, Popover } from '@appwrite.io/pink-svelte';
|
||||
import { IconFilter } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let query = '[]';
|
||||
@@ -117,19 +117,17 @@
|
||||
</script>
|
||||
|
||||
<div class="is-not-mobile">
|
||||
<Drop bind:show={showFiltersDesktop} noArrow>
|
||||
<slot {disabled} toggle={toggleDropdown}>
|
||||
<Button secondary on:click={toggleDropdown} {disabled}>
|
||||
<Icon icon={IconFilter} slot="start" size="s" />
|
||||
Filters
|
||||
{#if applied > 0}
|
||||
<span class="inline-tag">
|
||||
{applied}
|
||||
</span>
|
||||
{/if}
|
||||
</Button>
|
||||
</slot>
|
||||
<svelte:fragment slot="list">
|
||||
<Popover let:toggle placement="bottom-start">
|
||||
<Button secondary on:click={toggle} {disabled}>
|
||||
<Icon icon={IconFilter} slot="start" size="s" />
|
||||
Filters
|
||||
{#if applied > 0}
|
||||
<span class="inline-tag">
|
||||
{applied}
|
||||
</span>
|
||||
{/if}
|
||||
</Button>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<div class="dropped card">
|
||||
{#if displayQuickFilters}
|
||||
<slot name="quick" />
|
||||
@@ -171,7 +169,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Drop>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div class="is-only-mobile">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script>
|
||||
import { Heading } from '$lib/components';
|
||||
import { Card } from '@appwrite.io/pink-svelte';
|
||||
export let href;
|
||||
</script>
|
||||
|
||||
<a class="card" {href}>
|
||||
<Card.Link class="card" {href}>
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3"><slot name="eyebrow" /></div>
|
||||
@@ -26,7 +27,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Card.Link>
|
||||
|
||||
<style>
|
||||
/* TODO: remove this when ui library is updated*/
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<Copy {value} {event}>
|
||||
<Tag size="xs" variant="code">
|
||||
<Icon icon={IconDuplicate} size="s" />
|
||||
<Icon icon={IconDuplicate} size="s" slot="start" />
|
||||
<span
|
||||
style:white-space="nowrap"
|
||||
style:overflow="hidden"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
let slotTitle: HTMLSpanElement;
|
||||
</script>
|
||||
|
||||
<Tooltip disabled={!tooltipText || !tooltipShow} inline={false}>
|
||||
<Tooltip disabled={!tooltipText || !tooltipShow}>
|
||||
<Card.Selector
|
||||
{src}
|
||||
{alt}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { isCloud } from '$lib/system.js';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
let showSupport = false;
|
||||
|
||||
@@ -68,6 +69,10 @@
|
||||
: 'light'
|
||||
: theme;
|
||||
|
||||
trackEvent('select_theme', {
|
||||
value: theme === 'auto' ? 'system' : theme
|
||||
});
|
||||
|
||||
app.update(() => ({
|
||||
themeInUse: themeInUse,
|
||||
theme: theme
|
||||
@@ -115,7 +120,7 @@
|
||||
</a>
|
||||
<Breadcrumbs {organizations} />
|
||||
{#if selectedProject && selectedProject.pingCount === 0}
|
||||
<div class="only-desktop">
|
||||
<div class="only-desktop" style:margin-inline-start="-10px">
|
||||
<Button.Anchor
|
||||
href={`${base}/project-${selectedProject.$id}/get-started`}
|
||||
variant="secondary"
|
||||
@@ -130,12 +135,24 @@
|
||||
<Button.Anchor
|
||||
size="s"
|
||||
variant="primary"
|
||||
on:click={() => {
|
||||
trackEvent('click_organization_upgrade', {
|
||||
from: 'button',
|
||||
source: 'top_nav'
|
||||
});
|
||||
}}
|
||||
href={`${base}/organization-${currentOrg.$id}/change-plan`}
|
||||
>Upgrade</Button.Anchor>
|
||||
{/if}
|
||||
|
||||
<DropList show={$feedback.show} class="extended-width">
|
||||
<Button.Button type="button" variant="compact" on:click={() => toggleFeedback()}
|
||||
<Button.Button
|
||||
type="button"
|
||||
variant="compact"
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent('click_menu_feedback', { source: 'top_nav' });
|
||||
}}
|
||||
>Feedback
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
@@ -152,7 +169,10 @@
|
||||
<Button.Button
|
||||
variant="compact"
|
||||
type="button"
|
||||
on:click={() => (showSupport = !showSupport)}>
|
||||
on:click={() => {
|
||||
showSupport = !showSupport;
|
||||
trackEvent('click_menu_support', { source: 'top_nav' });
|
||||
}}>
|
||||
Support
|
||||
</Button.Button>
|
||||
|
||||
@@ -162,7 +182,7 @@
|
||||
</DropList>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row">
|
||||
<Tooltip inline={false}>
|
||||
<Tooltip>
|
||||
<Button.Button
|
||||
variant="text"
|
||||
aria-label="Toggle Command Center"
|
||||
@@ -174,6 +194,9 @@
|
||||
<Link.Button
|
||||
on:click={() => {
|
||||
showAccountMenu = !showAccountMenu;
|
||||
if (showAccountMenu) {
|
||||
trackEvent('click_menu_dropdown');
|
||||
}
|
||||
}}>
|
||||
<div style:user-select="none">
|
||||
<Avatar size="s" src={avatar} />
|
||||
@@ -352,10 +375,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
:global(.icons div:first-of-type:not(:has(.progress-card))) {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.account-container {
|
||||
position: absolute;
|
||||
right: var(--space-7);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { DropList, DropListItem } from '..';
|
||||
import Label from './label.svelte';
|
||||
import Custom from './custom.svelte';
|
||||
import Team from './team.svelte';
|
||||
import User from './user.svelte';
|
||||
import type { Permission } from './permissions.svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { ActionMenu, Popover } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let showDropdown: boolean;
|
||||
export let showUser: boolean;
|
||||
export let showTeam: boolean;
|
||||
export let showLabel: boolean;
|
||||
@@ -16,34 +15,43 @@
|
||||
export let groups: Writable<Map<string, Permission>>;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: if (showUser || showTeam || showCustom) {
|
||||
showDropdown = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropList bind:show={showDropdown} placement="bottom-end" fixed>
|
||||
<slot />
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem disabled={$groups.has('any')} on:click={() => dispatch('create', ['any'])}>
|
||||
Any
|
||||
</DropListItem>
|
||||
<DropListItem
|
||||
disabled={$groups.has('guests')}
|
||||
on:click={() => dispatch('create', ['guests'])}>
|
||||
All guests
|
||||
</DropListItem>
|
||||
<DropListItem
|
||||
disabled={$groups.has('users')}
|
||||
on:click={() => dispatch('create', ['users'])}>
|
||||
All users
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => (showUser = true)}>Select users</DropListItem>
|
||||
<DropListItem on:click={() => (showTeam = true)}>Select teams</DropListItem>
|
||||
<DropListItem on:click={() => (showLabel = true)}>Label</DropListItem>
|
||||
<DropListItem on:click={() => (showCustom = true)}>Custom permission</DropListItem>
|
||||
<Popover let:toggle>
|
||||
<slot {toggle} />
|
||||
<svelte:fragment slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
disabled={$groups.has('any')}
|
||||
on:click={() => dispatch('create', ['any'])}>
|
||||
Any
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
disabled={$groups.has('any')}
|
||||
on:click={() => dispatch('create', ['any'])}>
|
||||
Any
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
disabled={$groups.has('guests')}
|
||||
on:click={() => dispatch('create', ['guests'])}>
|
||||
All guests
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
disabled={$groups.has('users')}
|
||||
on:click={() => dispatch('create', ['users'])}>
|
||||
All users
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => (showUser = true)}
|
||||
>Select users</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => (showTeam = true)}
|
||||
>Select teams</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => (showLabel = true)}
|
||||
>Label</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => (showCustom = true)}
|
||||
>Custom permission</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</Popover>
|
||||
|
||||
<User bind:show={showUser} on:create {groups} />
|
||||
<Team
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
let showTeam = false;
|
||||
let showLabel = false;
|
||||
let showCustom = false;
|
||||
let showDropdown = false;
|
||||
|
||||
const groups = writable<Map<string, Permission>>(new Map());
|
||||
|
||||
@@ -71,8 +70,6 @@
|
||||
|
||||
return n;
|
||||
});
|
||||
|
||||
showDropdown = false;
|
||||
}
|
||||
|
||||
function fromPermissionString(permission: string): void {
|
||||
@@ -212,12 +209,12 @@
|
||||
<Actions
|
||||
bind:showLabel
|
||||
bind:showCustom
|
||||
bind:showDropdown
|
||||
bind:showTeam
|
||||
bind:showUser
|
||||
{groups}
|
||||
on:create={create}>
|
||||
<Button text on:click={() => (showDropdown = !showDropdown)}>
|
||||
on:create={create}
|
||||
let:toggle>
|
||||
<Button text on:click={toggle}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add role
|
||||
</Button>
|
||||
@@ -229,12 +226,12 @@
|
||||
<Actions
|
||||
bind:showLabel
|
||||
bind:showCustom
|
||||
bind:showDropdown
|
||||
bind:showTeam
|
||||
bind:showUser
|
||||
{groups}
|
||||
on:create={create}>
|
||||
<Button secondary icon on:click={() => (showDropdown = !showDropdown)}>
|
||||
on:create={create}
|
||||
let:toggle>
|
||||
<Button secondary icon on:click={toggle}>
|
||||
<Icon icon={IconPlus} size="s" />
|
||||
</Button>
|
||||
</Actions>
|
||||
|
||||
@@ -117,12 +117,12 @@
|
||||
<Actions
|
||||
bind:showLabel
|
||||
bind:showCustom
|
||||
bind:showDropdown
|
||||
bind:showTeam
|
||||
bind:showUser
|
||||
{groups}
|
||||
on:create={create}>
|
||||
<Button text on:click={() => (showDropdown = !showDropdown)}>
|
||||
on:create={create}
|
||||
let:toggle>
|
||||
<Button text on:click={toggle}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add role
|
||||
</Button>
|
||||
@@ -134,12 +134,12 @@
|
||||
<Actions
|
||||
bind:showLabel
|
||||
bind:showCustom
|
||||
bind:showDropdown
|
||||
bind:showTeam
|
||||
bind:showUser
|
||||
{groups}
|
||||
on:create={create}>
|
||||
<Button secondary icon on:click={() => (showDropdown = !showDropdown)}>
|
||||
on:create={create}
|
||||
let:toggle>
|
||||
<Button secondary icon on:click={toggle}>
|
||||
<Icon icon={IconPlus} size="s" />
|
||||
</Button>
|
||||
</Actions>
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
color: hsl(var(--color-neutral-50));
|
||||
line-height: 1.25rem;
|
||||
&:first-child {
|
||||
color: hsl(var(--color-neutral-70));
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) div {
|
||||
color: hsl(var(--color-neutral-20));
|
||||
|
||||
&:first-child {
|
||||
color: hsl(var(--color-neutral-10));
|
||||
}
|
||||
max-width: 360px;
|
||||
text-wrap: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,58 +5,62 @@
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { Badge, Layout, Link, Tag, Typography } from '@appwrite.io/pink-svelte';
|
||||
</script>
|
||||
|
||||
<Base>
|
||||
{#if isCloud}
|
||||
{#if $organization?.billingPlan !== BillingPlan.FREE}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<p>
|
||||
<span class="u-bold">Roles</span>
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
<span class="inline-tag u-normal u-x-small">Pro plan</span>
|
||||
{/if}
|
||||
</p>
|
||||
<p>Owner, Developer, Editor, Analyst and Billing.</p>
|
||||
</div>
|
||||
<p>
|
||||
<Button link external href="https://appwrite.io/docs/advanced/platform/roles"
|
||||
>Learn more</Button> about roles.
|
||||
</p>
|
||||
{:else}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<p>
|
||||
<span class="u-bold">Roles</span>
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
<span class="inline-tag u-normal u-x-small">Pro plan</span>
|
||||
{/if}
|
||||
</p>
|
||||
<p>
|
||||
<Layout.Stack gap="s">
|
||||
{#if isCloud}
|
||||
{#if $organization?.billingPlan !== BillingPlan.FREE}
|
||||
<Typography.Text variant="m-600">Roles</Typography.Text>
|
||||
<Typography.Text>Owner, Developer, Editor, Analyst and Billing.</Typography.Text>
|
||||
<Typography.Text>
|
||||
<Link.Anchor
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://appwrite.io/docs/advanced/platform/roles"
|
||||
>Learn more</Link.Anchor>
|
||||
</Typography.Text>
|
||||
{:else}
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Typography.Text variant="m-600">
|
||||
<span class="u-bold">Roles</span>
|
||||
</Typography.Text>
|
||||
<Badge variant="secondary" size="xs" content="Pro plan" />
|
||||
</Layout.Stack>
|
||||
<Typography.Text>
|
||||
Upgrade to Pro to assign new roles to members such as Owner, Developer, Editor
|
||||
or Analyst.
|
||||
</Typography.Text>
|
||||
|
||||
<p class="u-flex u-main-end u-cross-center u-gap-4">
|
||||
<Button
|
||||
size="s"
|
||||
text
|
||||
external
|
||||
href="https://appwrite.io/docs/advanced/platform/roles">Learn more</Button>
|
||||
<Button size="s" secondary external href={$upgradeURL}>Upgrade plan</Button>
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Typography.Text variant="m-600">
|
||||
<span class="u-bold">Roles</span>
|
||||
</Typography.Text>
|
||||
<Badge variant="secondary" size="xs" content="Cloud" />
|
||||
</Layout.Stack>
|
||||
<Typography.Text>
|
||||
Upgrade to Cloud to assign new roles to members or ask us about our enterprise self
|
||||
hosted offering.
|
||||
</Typography.Text>
|
||||
<p class="u-flex u-main-end u-cross-center u-gap-4">
|
||||
<Button text external href="https://appwrite.io/docs/advanced/platform/roles"
|
||||
>Learn more</Button>
|
||||
<Button secondary external href={$upgradeURL}>Upgrade plan</Button>
|
||||
<Button
|
||||
size="s"
|
||||
text
|
||||
external
|
||||
href="https://appwrite.io/docs/advanced/platform/roles">Learn more</Button>
|
||||
<Button size="s" secondary external href={$upgradeURL}>Upgrade to Cloud</Button>
|
||||
</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<p>
|
||||
<span class="u-bold">Roles</span>
|
||||
<span class="inline-tag u-normal u-x-small">Appwrite Cloud</span>
|
||||
</p>
|
||||
<p>
|
||||
Upgrade to Cloud to assign new roles to members or ask us about our entriprise self
|
||||
hosted offering.
|
||||
</p>
|
||||
</div>
|
||||
<p class="u-flex u-main-end u-cross-center u-gap-4">
|
||||
<Button text external href="https://appwrite.io/docs/advanced/platform/roles"
|
||||
>Learn more</Button>
|
||||
<Button secondary external href={$upgradeURL}>Upgrade to Cloud</Button>
|
||||
</p>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Base>
|
||||
|
||||
@@ -1,37 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { IconDuplicate } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Copy } from '.';
|
||||
import { Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { Input } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show = false;
|
||||
export let value: string;
|
||||
export let copyEvent: string = null;
|
||||
</script>
|
||||
|
||||
<div class="interactive-text-output" class:is-textarea={show} data-private>
|
||||
{#if show}
|
||||
<span class="text u-line-height-1-5 u-break-all">{value}</span>
|
||||
{:else}
|
||||
<span class="text">••••••••••••</span>
|
||||
{/if}
|
||||
<div class="u-flex u-cross-child-start u-gap-8">
|
||||
<Tooltip>
|
||||
<button
|
||||
class="interactive-text-output-button"
|
||||
aria-label="show hidden text"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
show = !show;
|
||||
trackEvent(`click_secret_${show ? 'show' : 'hide'}`);
|
||||
}}>
|
||||
<span class:icon-eye-off={show} class:icon-eye={!show} aria-hidden="true" />
|
||||
</button>
|
||||
<span slot="tooltip">{show ? 'Hide secret' : 'Show secret'}</span>
|
||||
</Tooltip>
|
||||
<Input.Password {value}>
|
||||
<div slot="end">
|
||||
<Copy {value} event={copyEvent} eventContext="click_secret_copy">
|
||||
<button class="interactive-text-output-button" aria-label="copy text" type="button">
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
</button>
|
||||
<Input.Action icon={IconDuplicate} />
|
||||
</Copy>
|
||||
</div>
|
||||
</div>
|
||||
</Input.Password>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
import MobileFeedbackModal from '$routes/(console)/wizard/feedback/mobileFeedbackModal.svelte';
|
||||
import { getSidebarState, updateSidebarState } from '$lib/helpers/sidebar';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
type $$Props = HTMLElement & {
|
||||
state?: 'closed' | 'open' | 'icons';
|
||||
@@ -66,6 +67,7 @@
|
||||
|
||||
$: state = $isTabletViewport ? 'closed' : getSidebarState();
|
||||
$: pathname = $page.url.pathname;
|
||||
$: isOnProjectSettings = /^\/console\/project-[a-zA-Z0-9-]+\/settings$/.test(pathname);
|
||||
|
||||
const projectOptions = [
|
||||
{ name: 'Auth', icon: IconUserGroup, slug: 'auth' },
|
||||
@@ -106,11 +108,12 @@
|
||||
</div>
|
||||
<div slot="middle" class:icons={state === 'icons'}>
|
||||
{#if progressCard}
|
||||
<Tooltip inline={false} placement="right" disabled={state !== 'icons'}>
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
class="progress-card"
|
||||
href={`/console/project-${project.$id}/get-started`}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_get_started');
|
||||
sideBarIsOpen = false;
|
||||
}}>
|
||||
<div class="progressCircle">
|
||||
@@ -132,12 +135,13 @@
|
||||
{/if}
|
||||
{#if project}
|
||||
<Layout.Stack direction="column" gap="s">
|
||||
<Tooltip inline={false} placement="right" disabled={state !== 'icons'}>
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
href={`/console/project-${project.$id}/overview`}
|
||||
class="link"
|
||||
class:active={pathname.includes('platforms')}
|
||||
class:active={pathname.includes('overview')}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_overview');
|
||||
sideBarIsOpen = false;
|
||||
}}
|
||||
><span class="link-icon"
|
||||
@@ -159,12 +163,13 @@
|
||||
></span>
|
||||
</div>
|
||||
{#each projectOptions as projectOption}
|
||||
<Tooltip inline={false} placement="right" disabled={state !== 'icons'}>
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
href={`/console/project-${project.$id}/${projectOption.slug}`}
|
||||
class="link"
|
||||
class:active={pathname.includes(projectOption.slug)}
|
||||
on:click={() => {
|
||||
trackEvent(`click_menu_${projectOption.slug}`);
|
||||
sideBarIsOpen = false;
|
||||
}}
|
||||
><span class="link-icon"
|
||||
@@ -181,8 +186,13 @@
|
||||
<Divider />
|
||||
</div>
|
||||
<div class="only-mobile">
|
||||
<Tooltip inline={false} placement="right" disabled={state !== 'icons'}>
|
||||
<a href={`/console/project-${project.$id}/settings`} class="link"
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
href={`/console/project-${project.$id}/settings`}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_settings');
|
||||
}}
|
||||
class="link"
|
||||
><span class="link-icon"><Icon icon={IconCog} size="s" /></span
|
||||
><span
|
||||
class:no-text={state === 'icons'}
|
||||
@@ -200,7 +210,10 @@
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => toggleFeedback()}
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent('click_menu_feedback', { source: 'side_nav' });
|
||||
}}
|
||||
>Feedback
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
@@ -212,7 +225,10 @@
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => ($showSupportModal = true)}>
|
||||
on:click={() => {
|
||||
$showSupportModal = true;
|
||||
trackEvent('click_menu_support', { source: 'side_nav' });
|
||||
}}>
|
||||
<span>Support</span>
|
||||
|
||||
<svelte:fragment slot="other">
|
||||
@@ -228,11 +244,14 @@
|
||||
<div slot="bottom" class="bottom" class:icons={state === 'icons'}>
|
||||
{#if project}
|
||||
<div class="only-desktop">
|
||||
<Tooltip inline={false} placement="right" disabled={state !== 'icons'}>
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
href={`/console/project-${project.$id}/settings`}
|
||||
class="link"
|
||||
class:active={pathname.includes('settings')}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_settings');
|
||||
}}
|
||||
class:active={isOnProjectSettings}
|
||||
><span class="link-icon"><Icon icon={IconCog} size="s" /></span><span
|
||||
class:no-text={state === 'icons'}
|
||||
class:has-text={state === 'open'}
|
||||
@@ -251,8 +270,11 @@
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => toggleFeedback()}
|
||||
>Feedback
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent('click_menu_feedback', { source: 'side_nav' });
|
||||
}}
|
||||
><span>Feedback</span>
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
<MobileFeedbackModal />
|
||||
@@ -263,7 +285,10 @@
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => ($showSupportModal = true)}>
|
||||
on:click={() => {
|
||||
$showSupportModal = true;
|
||||
trackEvent('click_menu_support', { source: 'side_nav' });
|
||||
}}>
|
||||
<span>Support</span>
|
||||
|
||||
<svelte:fragment slot="other">
|
||||
@@ -279,6 +304,7 @@
|
||||
</div>
|
||||
</Sidebar.Base>
|
||||
</div>
|
||||
|
||||
{#if subNavigation}
|
||||
<div class="sub-navigation" class:icons={state === 'icons'}>
|
||||
<svelte:component this={subNavigation} />
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let selected = false;
|
||||
export let href: string = null;
|
||||
export let event: string = null;
|
||||
export let noscroll = false;
|
||||
export let root: { variant: Variant; stretch: boolean } = {
|
||||
variant: 'primary',
|
||||
stretch: false
|
||||
@@ -87,6 +88,7 @@
|
||||
|
||||
{#if href}
|
||||
<Tabs.Item.Link
|
||||
{noscroll}
|
||||
{root}
|
||||
{href}
|
||||
bind:active={selected}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
let container: HTMLSpanElement | null;
|
||||
</script>
|
||||
|
||||
<Tooltip maxWidth="auto" inline={false}>
|
||||
<Tooltip maxWidth="auto">
|
||||
<span class={`text ${alternativeTrim ? 'u-trim-1' : 'u-trim'}`} bind:this={container}>
|
||||
<slot />
|
||||
</span>
|
||||
|
||||
@@ -3,19 +3,20 @@
|
||||
import { Layout, Skeleton, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let description: string;
|
||||
export let value: string = null;
|
||||
export let value: string | number = null;
|
||||
</script>
|
||||
|
||||
<Card radius="s" padding="xs" isTile>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-tertiary">
|
||||
<Layout.Stack gap="xxxs">
|
||||
<Typography.Caption variant="400" color="--color-fgcolor-neutral-tertiary">
|
||||
{description}
|
||||
</Typography.Text>
|
||||
</Typography.Caption>
|
||||
<slot>
|
||||
{#if value !== null && value !== undefined}
|
||||
<Typography.Title size="s" truncate>{value}</Typography.Title>
|
||||
<Typography.Text size="s" truncate color="--color-fgcolor-neutral-primary"
|
||||
>{value}</Typography.Text>
|
||||
{:else}
|
||||
<Skeleton variant="line" width={100} height={26} />
|
||||
<Skeleton variant="line" width={100} height={19.5} />
|
||||
{/if}
|
||||
</slot>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
{#if !hideColumns && view === View.Table}
|
||||
{#if $columns?.length}
|
||||
<Popover let:toggle>
|
||||
<Popover let:toggle placement="bottom-end">
|
||||
<Button.Button
|
||||
size="s"
|
||||
variant="secondary"
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
export let submissionLoader = false;
|
||||
export let forceShowLoader = false;
|
||||
export let compact = false;
|
||||
export let extraCompact = false;
|
||||
|
||||
const isSubmitting = hasContext('form')
|
||||
? getContext<FormContext>('form').isSubmitting
|
||||
@@ -47,7 +48,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getVariant(): 'primary' | 'secondary' | 'text' | 'compact' | 'danger' {
|
||||
function getVariant():
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'text'
|
||||
| 'compact'
|
||||
| 'danger'
|
||||
| 'extra-compact' {
|
||||
switch (true) {
|
||||
case secondary:
|
||||
return 'secondary';
|
||||
@@ -55,6 +62,8 @@
|
||||
return 'text';
|
||||
case compact:
|
||||
return 'compact';
|
||||
case extraCompact:
|
||||
return 'extra-compact';
|
||||
case danger:
|
||||
return 'danger';
|
||||
default:
|
||||
@@ -86,8 +95,7 @@
|
||||
target={external ? '_blank' : ''}
|
||||
rel={external ? 'noopener noreferrer' : ''}
|
||||
class={resolvedClasses}
|
||||
aria-label={ariaLabel}
|
||||
--button-width={fullWidth ? '100%' : 'max-content'}>
|
||||
aria-label={ariaLabel}>
|
||||
<slot name="start" slot="start" />
|
||||
<slot />
|
||||
<slot slot="end" name="end" />
|
||||
@@ -104,8 +112,7 @@
|
||||
{variant}
|
||||
class={resolvedClasses}
|
||||
aria-label={ariaLabel}
|
||||
type={submit ? 'submit' : 'button'}
|
||||
--button-width={fullWidth ? '100%' : 'max-content'}>
|
||||
type={submit ? 'submit' : 'button'}>
|
||||
<slot name="start" slot="start" />
|
||||
{#if ($isSubmitting && submissionLoader) || (forceShowLoader && submissionLoader)}
|
||||
<span
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
element?: HTMLInputElement | undefined;
|
||||
indeterminate?: boolean;
|
||||
size?: 's' | 'm';
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export let id: string = '';
|
||||
@@ -20,6 +21,7 @@
|
||||
export let disabled = false;
|
||||
export let element: HTMLInputElement | undefined = undefined;
|
||||
export let size: $$Props['size'] = 's';
|
||||
export let description = '';
|
||||
let error: string;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
@@ -47,6 +49,7 @@
|
||||
{size}
|
||||
{label}
|
||||
{required}
|
||||
{description}
|
||||
on:invalid={handleInvalid}
|
||||
on:click
|
||||
on:change />
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
{placeholder}
|
||||
{disabled}
|
||||
{isSearchable}
|
||||
{required}
|
||||
helper={error}
|
||||
state={error ? 'error' : 'default'}
|
||||
on:invalid={handleInvalid}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
export let disabled = false;
|
||||
export let readonly = false;
|
||||
export let autofocus = false;
|
||||
export let spellcheck: boolean = undefined;
|
||||
export let maxlength: number = null;
|
||||
export let rows = 3;
|
||||
export let helper: string = '';
|
||||
@@ -45,6 +46,8 @@
|
||||
autofocus={autofocus || undefined}
|
||||
helper={error || helper}
|
||||
state={error ? 'error' : 'default'}
|
||||
{spellcheck}
|
||||
autocorrect={spellcheck ? undefined : 'off'}
|
||||
on:invalid={handleInvalid}
|
||||
on:input
|
||||
bind:value />
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { ActionMenu, Layout } from '@appwrite.io/pink-svelte';
|
||||
import InputCheckbox from './forms/inputCheckbox.svelte';
|
||||
|
||||
export let value = false;
|
||||
export let padding: number | null = null;
|
||||
</script>
|
||||
|
||||
<li class="drop-list-item">
|
||||
<button
|
||||
class="drop-button u-flex u-cross-center u-gap-12"
|
||||
style:--button-padding-horizontal={padding ? `${padding / 16}rem` : ''}
|
||||
style:--button-padding-vertical={padding ? `${padding / 16}rem` : ''}
|
||||
on:click|preventDefault
|
||||
type="button">
|
||||
<input type="checkbox" class="is-small" bind:checked={value} />
|
||||
<ActionMenu.Item.Button on:click>
|
||||
<Layout.Stack direction="row" gap="xs" alignItems="center">
|
||||
<InputCheckbox bind:checked={value} id="filter" size="s" />
|
||||
|
||||
<slot />
|
||||
</button>
|
||||
</li>
|
||||
</Layout.Stack>
|
||||
</ActionMenu.Item.Button>
|
||||
|
||||
@@ -165,13 +165,10 @@
|
||||
<svelte:component this={$page.data.header} />
|
||||
{/if}
|
||||
<slot />
|
||||
{#if showFooter && showSideNavigation}
|
||||
{#if showFooter}
|
||||
<slot name="footer" />
|
||||
{/if}
|
||||
</section>
|
||||
{#if showFooter && !showSideNavigation}
|
||||
<slot name="footer" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Heading } from '$lib/components';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
/**
|
||||
@@ -20,18 +21,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="form-header" class:hide-divider={!$$slots.subtitle}>
|
||||
<Heading tag="h1" size="6">
|
||||
<slot name="title" />
|
||||
</Heading>
|
||||
{#if $$slots.subtitle}
|
||||
<p>
|
||||
<slot name="subtitle" />
|
||||
</p>
|
||||
{/if}
|
||||
</header>
|
||||
<Layout.Stack>
|
||||
<header class="form-header" class:hide-divider={!$$slots.subtitle}>
|
||||
<Heading tag="h1" size="6">
|
||||
<slot name="title" />
|
||||
</Heading>
|
||||
{#if $$slots.subtitle}
|
||||
<p>
|
||||
<slot name="subtitle" />
|
||||
</p>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<slot />
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
|
||||
<style>
|
||||
.hide-divider {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { createEventDispatcher, type SvelteComponent } from 'svelte';
|
||||
import WizardExitModal from './wizardExitModal.svelte';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let title: string;
|
||||
export let steps: WizardStepsType;
|
||||
@@ -192,38 +193,40 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="wizard-main">
|
||||
<Form noStyle onSubmit={submit}>
|
||||
{#each sortedSteps as [step, { component }]}
|
||||
{#if $wizard.step === step}
|
||||
<svelte:component this={component} />
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="u-z-index-5 form-footer">
|
||||
<div class="u-flex u-main-end u-gap-12">
|
||||
{#if !isLastStep && currentStep?.optional}
|
||||
<Button text on:click={() => dispatch('finish')}>
|
||||
Skip optional steps
|
||||
<Form onSubmit={submit}>
|
||||
<Layout.Stack>
|
||||
{#each sortedSteps as [step, { component }]}
|
||||
{#if $wizard.step === step}
|
||||
<svelte:component this={component} />
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="u-z-index-5 form-footer">
|
||||
<div class="u-flex u-main-end u-gap-12">
|
||||
{#if !isLastStep && currentStep?.optional}
|
||||
<Button text on:click={() => dispatch('finish')}>
|
||||
Skip optional steps
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
{#if $wizard.step === 1}
|
||||
<Button secondary on:click={handleExit}>Cancel</Button>
|
||||
{:else}
|
||||
<Button secondary on:click={previousStep}>Back</Button>
|
||||
{/if}
|
||||
|
||||
{#if currentStep?.actions}
|
||||
{#each currentStep.actions as action}
|
||||
<Button secondary on:click={action.onClick}>
|
||||
{action.label}</Button>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<Button submit disabled={$wizard.nextDisabled}>
|
||||
{isLastStep ? finalAction : 'Next'}
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
{#if $wizard.step === 1}
|
||||
<Button secondary on:click={handleExit}>Cancel</Button>
|
||||
{:else}
|
||||
<Button secondary on:click={previousStep}>Back</Button>
|
||||
{/if}
|
||||
|
||||
{#if currentStep?.actions}
|
||||
{#each currentStep.actions as action}
|
||||
<Button secondary on:click={action.onClick}>
|
||||
{action.label}</Button>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<Button submit disabled={$wizard.nextDisabled}>
|
||||
{isLastStep ? finalAction : 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import type { Dependencies } from '$lib/constants';
|
||||
import Confirm from '$lib/components/confirm.svelte';
|
||||
|
||||
@@ -15,6 +13,18 @@
|
||||
|
||||
let error: string;
|
||||
|
||||
function getCorrectMessage(): string {
|
||||
const resourceType = selectedDomain.resourceType;
|
||||
switch (resourceType) {
|
||||
case 'api':
|
||||
return `access ${resourceType}`;
|
||||
case 'function':
|
||||
return `execute your ${resourceType}`;
|
||||
case 'site':
|
||||
return `view your ${resourceType} `;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteDomain() {
|
||||
try {
|
||||
await sdk.forProject.proxy.deleteRule(selectedDomain.$id);
|
||||
@@ -32,8 +42,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
a
|
||||
<Confirm onSubmit={deleteDomain} title="Delete domain" bind:open={showDelete} bind:error>
|
||||
Are you sure you want to delete <b>{selectedDomain?.domain}</b>? You will no longer be able to
|
||||
execute your function by visiting this domain.
|
||||
<p data-private>Are you sure you want to delete <b>{selectedDomain?.domain}</b>?</p>
|
||||
<p>You will no longer be able to {getCorrectMessage()} by visiting this domain.</p>
|
||||
</Confirm>
|
||||
|
||||
@@ -55,10 +55,6 @@
|
||||
$templateConfig.repositoryBehaviour === 'manual'
|
||||
? undefined
|
||||
: $choices.rootDir || undefined,
|
||||
$template.providerRepositoryId || undefined,
|
||||
$template.providerOwner || undefined,
|
||||
runtimeDetail.providerRootDirectory || undefined,
|
||||
$template.providerVersion || undefined,
|
||||
$templateConfig.specification || undefined
|
||||
);
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import Confirm from '$lib/components/confirm.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dialog, Layout } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let showDelete = false;
|
||||
let error: string;
|
||||
|
||||
@@ -38,6 +38,6 @@
|
||||
title="Delete authentication method"
|
||||
bind:open={showDelete}
|
||||
bind:error>
|
||||
Are you sure you want to delete this authentication method? You will no longer be able to use
|
||||
this method to authenticate your account.
|
||||
<p>Are you sure you want to delete this authentication method?</p>
|
||||
<p>You will no longer be able to use this method to authenticate your account.</p>
|
||||
</Confirm>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Submit, trackEvent } from '$lib/actions/analytics';
|
||||
import { Heading, Trim } from '$lib/components';
|
||||
import { Trim } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Card, Heading } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import { tierToPlan, type Tier, plansInfo } from '$lib/stores/billing';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { base } from '$app/paths';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
|
||||
let name: string;
|
||||
let plan: Tier;
|
||||
|
||||
const options = isCloud
|
||||
? [
|
||||
{
|
||||
value: BillingPlan.FREE,
|
||||
label: `${tierToPlan(BillingPlan.FREE).name} - ${formatCurrency($plansInfo.get(BillingPlan.FREE).price)} / month`
|
||||
},
|
||||
{
|
||||
value: BillingPlan.PRO,
|
||||
label: `${tierToPlan(BillingPlan.PRO).name} - ${formatCurrency($plansInfo.get(BillingPlan.PRO).price)} / month + add-ons`
|
||||
},
|
||||
{
|
||||
value: BillingPlan.SCALE,
|
||||
label: `${tierToPlan(BillingPlan.SCALE).name} - ${formatCurrency($plansInfo.get(BillingPlan.SCALE).price)}/month + usage`
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
onMount(() => {
|
||||
if (isCloud) {
|
||||
checkPricingRefAndRedirect($page.url.searchParams);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
const orgName = name?.length ? name : 'Personal Projects';
|
||||
if (isCloud) {
|
||||
if (plan === BillingPlan.FREE) {
|
||||
try {
|
||||
const org = await sdk.forConsole.billing.createOrganization(
|
||||
ID.unique(),
|
||||
orgName,
|
||||
plan,
|
||||
null,
|
||||
null
|
||||
);
|
||||
trackEvent(Submit.OrganizationCreate, {
|
||||
plan: tierToPlan(plan)?.name
|
||||
});
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await goto(`${base}/organization-${org.$id}`);
|
||||
addNotification({
|
||||
message: `${orgName} organization successfully created`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.OrganizationCreate);
|
||||
}
|
||||
} else {
|
||||
goto(`${base}/create-organization?name=${orgName}&plan=${plan}`);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const org = await sdk.forConsole.teams.create(ID.unique(), orgName);
|
||||
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await goto(`${base}/organization-${org.$id}`);
|
||||
|
||||
addNotification({
|
||||
message: `${orgName} organization successfully created`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.OrganizationCreate);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container overlapCover size="large">
|
||||
<Card>
|
||||
<Heading size="4" tag="h2">Create a new organization</Heading>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormList gap={16}>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Organization name"
|
||||
hideRequired
|
||||
bind:value={name} />
|
||||
{#if isCloud}
|
||||
<div class="u-margin-block-start-8">
|
||||
<h3><b>Plan</b></h3>
|
||||
<p>
|
||||
For more details on our plans, visit our <Button
|
||||
link
|
||||
external
|
||||
href="https://appwrite.io/pricing">pricing page</Button
|
||||
>.
|
||||
</p>
|
||||
<FormList class="u-margin-block-start-8">
|
||||
<InputSelect
|
||||
placeholder="Select a plan"
|
||||
label="Select plan"
|
||||
showLabel={false}
|
||||
id="plan"
|
||||
required
|
||||
hideRequired
|
||||
{options}
|
||||
bind:value={plan} />
|
||||
</FormList>
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
fullWidth
|
||||
submit
|
||||
disabled={isCloud && !plan}
|
||||
event="create_organization"
|
||||
submissionLoader
|
||||
let:isSubmitting>
|
||||
{#if isSubmitting}
|
||||
Creating your first organization
|
||||
{:else}
|
||||
Get started
|
||||
{/if}
|
||||
</Button>
|
||||
</FormList>
|
||||
</Form>
|
||||
</Card>
|
||||
</Container>
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import Create from './createProjectCloud.svelte';
|
||||
@@ -29,12 +28,19 @@
|
||||
import { openImportWizard } from '../project-[project]/settings/migrations/(import)';
|
||||
import { readOnly } from '$lib/stores/billing';
|
||||
import type { RegionList } from '$lib/sdk/billing';
|
||||
import { onMount } from 'svelte';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { onMount, type ComponentType } from 'svelte';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
IconCode,
|
||||
IconFlutter,
|
||||
IconPlus,
|
||||
IconReact,
|
||||
IconUnity
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import { getPlatformInfo } from '$lib/helpers/platform';
|
||||
|
||||
export let data;
|
||||
@@ -64,6 +70,26 @@
|
||||
if (isCloud) wizard.start(Create);
|
||||
else showCreate = true;
|
||||
}
|
||||
|
||||
function getIconForPlatform(platform: string): ComponentType {
|
||||
switch (platform) {
|
||||
case 'code':
|
||||
return IconCode;
|
||||
case 'flutter':
|
||||
return IconFlutter;
|
||||
case 'apple':
|
||||
return IconApple;
|
||||
case 'android':
|
||||
return IconAndroid;
|
||||
case 'react-native':
|
||||
return IconReact;
|
||||
case 'unity':
|
||||
return IconUnity;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Create project',
|
||||
@@ -118,100 +144,94 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $organization?.$id}
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<Heading tag="h2" size="5">Projects</Heading>
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<Heading tag="h2" size="5">Projects</Heading>
|
||||
|
||||
<DropList bind:show={showDropdown} placement="bottom-end">
|
||||
{#if $canWriteProjects}
|
||||
<Button
|
||||
on:click={handleCreateProject}
|
||||
event="create_project"
|
||||
disabled={$readOnly && !GRACE_PERIOD_OVERRIDE}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create project
|
||||
</Button>
|
||||
{/if}
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => (showCreate = true)}>Empty project</DropListItem>
|
||||
<DropListItem on:click={importProject}>
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
Import project <span class="tag eyebrow-heading-3">Experimental</span>
|
||||
</div>
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
<DropList bind:show={showDropdown} placement="bottom-end">
|
||||
{#if $canWriteProjects}
|
||||
<Button
|
||||
on:click={handleCreateProject}
|
||||
event="create_project"
|
||||
disabled={$readOnly && !GRACE_PERIOD_OVERRIDE}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create project
|
||||
</Button>
|
||||
{/if}
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => (showCreate = true)}>Empty project</DropListItem>
|
||||
<DropListItem on:click={importProject}>
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
Import project <span class="tag eyebrow-heading-3">Experimental</span>
|
||||
</div>
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
|
||||
{#if data.projects.total}
|
||||
<CardContainer
|
||||
showEmpty={$canWriteProjects}
|
||||
total={data.projects.total}
|
||||
offset={data.offset}
|
||||
on:click={handleCreateProject}>
|
||||
{#each data.projects.projects as project}
|
||||
{@const platforms = filterPlatforms(
|
||||
project.platforms.map((platform) => getPlatformInfo(platform.type))
|
||||
)}
|
||||
<li>
|
||||
<GridItem1 href={`${base}/project-${project.$id}`}>
|
||||
<svelte:fragment slot="eyebrow">
|
||||
{project?.platforms?.length ? project?.platforms?.length : 'No'} apps
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
{project.name}
|
||||
</svelte:fragment>
|
||||
{#if allServiceDisabled(project)}
|
||||
<p>
|
||||
<span class="icon-pause" aria-hidden="true" /> All services are disabled.
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#each platforms as platform, i}
|
||||
{#if i < 3}
|
||||
<Pill>
|
||||
<span class={`icon-${platform.icon}`} aria-hidden="true" />
|
||||
{platform.name}
|
||||
</Pill>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if platforms?.length > 3}
|
||||
<Pill>
|
||||
+{project.platforms.length - 3}
|
||||
</Pill>
|
||||
{/if}
|
||||
<svelte:fragment slot="icons">
|
||||
{#if isCloud && regions}
|
||||
{@const region = findRegion(project)}
|
||||
<span class="u-color-text-gray u-medium u-line-height-2">
|
||||
{region?.name}
|
||||
</span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</GridItem1>
|
||||
</li>
|
||||
{/each}
|
||||
<svelte:fragment slot="empty">
|
||||
<p>Create a new project</p>
|
||||
</svelte:fragment>
|
||||
</CardContainer>
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
allowCreate={$canWriteProjects}
|
||||
on:click={handleCreateProject}
|
||||
target="project"
|
||||
href="https://appwrite.io/docs/quick-starts"></Empty>
|
||||
{/if}
|
||||
|
||||
<PaginationWithLimit
|
||||
name="Projects"
|
||||
limit={data.limit}
|
||||
{#if data.projects.total}
|
||||
<CardContainer
|
||||
showEmpty={$canWriteProjects}
|
||||
total={data.projects.total}
|
||||
offset={data.offset}
|
||||
total={data.projects.total} />
|
||||
</Container>
|
||||
on:click={handleCreateProject}>
|
||||
{#each data.projects.projects as project}
|
||||
{@const platforms = filterPlatforms(
|
||||
project.platforms.map((platform) => getPlatformInfo(platform.type))
|
||||
)}
|
||||
<GridItem1 href={`${base}/project-${project.$id}`}>
|
||||
<svelte:fragment slot="eyebrow">
|
||||
{project?.platforms?.length ? project?.platforms?.length : 'No'} apps
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
{project.name}
|
||||
</svelte:fragment>
|
||||
{#if allServiceDisabled(project)}
|
||||
<p>
|
||||
<span class="icon-pause" aria-hidden="true" /> All services are disabled.
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<CreateOrganization bind:show={addOrganization} />
|
||||
<CreateProject bind:show={showCreate} teamId={$page.params.organization} />
|
||||
{/if}
|
||||
{#each platforms as platform, i}
|
||||
{#if i < 3}
|
||||
{@const icon = getIconForPlatform(platform.icon)}
|
||||
<Badge variant="secondary" content={platform.name}>
|
||||
<Icon {icon} size="s" slot="start" />
|
||||
</Badge>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if platforms?.length > 3}
|
||||
<Badge variant="secondary" content={`+${project.platforms.length - 3}`} />
|
||||
{/if}
|
||||
<svelte:fragment slot="icons">
|
||||
{#if isCloud && regions}
|
||||
{@const region = findRegion(project)}
|
||||
<span class="u-color-text-gray u-medium u-line-height-2">
|
||||
{region?.name}
|
||||
</span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</GridItem1>
|
||||
{/each}
|
||||
<svelte:fragment slot="empty">
|
||||
<p>Create a new project</p>
|
||||
</svelte:fragment>
|
||||
</CardContainer>
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
allowCreate={$canWriteProjects}
|
||||
on:click={handleCreateProject}
|
||||
target="project"
|
||||
href="https://appwrite.io/docs/quick-starts"></Empty>
|
||||
{/if}
|
||||
|
||||
<PaginationWithLimit
|
||||
name="Projects"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
total={data.projects.total} />
|
||||
</Container>
|
||||
|
||||
<CreateOrganization bind:show={addOrganization} />
|
||||
<CreateProject bind:show={showCreate} teamId={$page.params.organization} />
|
||||
|
||||
@@ -59,24 +59,24 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Invite member" {error} size="big" bind:show={showCreate} onSubmit={create}>
|
||||
<FormList>
|
||||
<InputEmail
|
||||
<Modal title="Invite member" {error} bind:show={showCreate} onSubmit={create}>
|
||||
<InputEmail
|
||||
required
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autofocus={true}
|
||||
bind:value={email} />
|
||||
<InputText id="member-name" label="Name" placeholder="Enter name" bind:value={name} />
|
||||
{#if isCloud}
|
||||
<InputSelect
|
||||
required
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autofocus={true}
|
||||
bind:value={email} />
|
||||
<InputText
|
||||
id="member-name"
|
||||
label="Name (optional)"
|
||||
placeholder="Enter name"
|
||||
bind:value={name} />
|
||||
{#if isCloud}
|
||||
<InputSelect popover={Roles} id="role" label="Role" options={roles} bind:value={role} />
|
||||
{/if}
|
||||
</FormList>
|
||||
popover={Roles}
|
||||
id="role"
|
||||
label="Role"
|
||||
options={roles}
|
||||
bind:value={role} />
|
||||
{/if}
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
|
||||
<Button submit submissionLoader>Send invite</Button>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ID, Region } from '@appwrite.io/console';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let show = false;
|
||||
@@ -49,21 +51,27 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Create project" {error} onSubmit={create} size="big" bind:show>
|
||||
<FormList>
|
||||
<Modal title="Create project" {error} onSubmit={create} bind:show>
|
||||
<Layout.Stack gap="l">
|
||||
<InputText id="name" label="Name" bind:value={name} required autofocus={true} />
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}>
|
||||
<span class="icon-pencil" aria-hidden="true" /><span class="text">
|
||||
Project ID
|
||||
</span>
|
||||
</Pill>
|
||||
</div>
|
||||
<span>
|
||||
<Tag size="s" on:click={() => (showCustomId = !showCustomId)}>
|
||||
<Icon icon={IconPencil} slot="start" size="s" />
|
||||
Project ID
|
||||
</Tag>
|
||||
</span>
|
||||
{:else}
|
||||
<CustomId autofocus bind:show={showCustomId} name="Project" isProject bind:id />
|
||||
<CustomId
|
||||
autofocus
|
||||
bind:show={showCustomId}
|
||||
name="Project"
|
||||
isProject
|
||||
bind:id
|
||||
fullWidth />
|
||||
{/if}
|
||||
</FormList>
|
||||
</Layout.Stack>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button submit {disabled}>Create</Button>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
let showDelete = false;
|
||||
let showRetry = false;
|
||||
let selectedDomain: Models.ProxyRule = null;
|
||||
let selectedDomain: Models.Domain = null;
|
||||
|
||||
$: console.log(data.domains);
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<Table.Header.Cell>Auto renewal</Table.Header.Cell>
|
||||
<Table.Header.Cell />
|
||||
</svelte:fragment>
|
||||
{#each data.domains.rules as domain}
|
||||
{#each data.domains.domains as domain}
|
||||
<Table.Link
|
||||
href={`${base}/organization-${$page.params.organization}/domains/domain-${domain.$id}`}>
|
||||
<Table.Cell>
|
||||
@@ -81,24 +81,26 @@
|
||||
<Icon icon={IconExternalLink} size="s" />
|
||||
</Layout.Stack>
|
||||
</Link>
|
||||
{#if domain.status === 'created'}
|
||||
{#if !domain.verified}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content="Verification failed"
|
||||
type="error" />
|
||||
{:else if domain.status === 'veryfing'}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content="Pending verification"
|
||||
type="warning" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{domain?.registrar ?? '-'}</Table.Cell>
|
||||
<Table.Cell>{domain?.nameservers ?? '-'}</Table.Cell>
|
||||
<Table.Cell>{domain?.expiry ?? '-'}</Table.Cell>
|
||||
<Table.Cell>{domain?.registrar || '-'}</Table.Cell>
|
||||
<Table.Cell>{domain?.nameservers || '-'}</Table.Cell>
|
||||
<Table.Cell>{domain?.expiry || '-'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{domain.renewAt ? toLocaleDateTime(domain.renewAt) : '-'}
|
||||
{domain.renewal ? toLocaleDateTime(domain.renewal) : '-'}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{domain.autoRenewal ? 'On' : 'Off'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
@@ -116,7 +118,7 @@
|
||||
|
||||
<svelte:fragment slot="tooltip" let:toggle>
|
||||
<ActionMenu.Root>
|
||||
{#if domain.status !== 'verified'}
|
||||
<!-- {#if domain.status !== 'verified'}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconRefresh}
|
||||
on:click={(e) => {
|
||||
@@ -127,7 +129,7 @@
|
||||
}}>
|
||||
Retry
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
{/if} -->
|
||||
<ActionMenu.Item.Button
|
||||
status="danger"
|
||||
leadingIcon={IconTrash}
|
||||
|
||||
@@ -20,14 +20,11 @@ export const load = async ({ depends, url, route }) => {
|
||||
limit,
|
||||
query,
|
||||
search,
|
||||
domains: await sdk.forProject.proxy.listRules(
|
||||
[
|
||||
Query.limit(limit),
|
||||
Query.offset(offset),
|
||||
Query.orderDesc(''),
|
||||
...parsedQueries.values()
|
||||
],
|
||||
search || undefined
|
||||
)
|
||||
domains: await sdk.forConsole.domains.list([
|
||||
Query.limit(limit),
|
||||
Query.offset(offset),
|
||||
Query.orderDesc(''),
|
||||
...parsedQueries.values()
|
||||
])
|
||||
};
|
||||
};
|
||||
|
||||
@@ -23,15 +23,11 @@
|
||||
const backPage = `${base}/project-${$page.params.project}/sites/site-${$page.params.site}/domains`;
|
||||
|
||||
let domain = '';
|
||||
let domainData: Models.ProxyRule;
|
||||
let domainData: Models.Domain;
|
||||
|
||||
async function addDomain() {
|
||||
try {
|
||||
domainData = await sdk.forProject.proxy.createRule(
|
||||
domain,
|
||||
ResourceType.Site,
|
||||
$page.params.site
|
||||
);
|
||||
domainData = await sdk.forConsole.domains.create($page.params.organization, domain);
|
||||
console.log(domainData);
|
||||
invalidate(Dependencies.SITES_DOMAINS);
|
||||
} catch (error) {
|
||||
@@ -44,7 +40,7 @@
|
||||
|
||||
async function verifyStatus() {
|
||||
try {
|
||||
domainData = await sdk.forProject.proxy.updateRuleVerification(domainData.$id);
|
||||
domainData = await sdk.forConsole.domains.updateNameservers(domainData.$id);
|
||||
console.log(domainData);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
@@ -56,7 +52,7 @@
|
||||
|
||||
async function back() {
|
||||
try {
|
||||
await sdk.forProject.proxy.deleteRule(domainData.$id);
|
||||
await sdk.forConsole.domains.delete(domainData.$id);
|
||||
domainData = undefined;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Button, InputCheckbox } from '$lib/elements/forms';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { invalidate } from '$app/navigation';
|
||||
@@ -9,13 +8,13 @@
|
||||
import { Confirm } from '$lib/components';
|
||||
|
||||
export let show = false;
|
||||
export let selectedDomain: Models.ProxyRule;
|
||||
let confirm = false;
|
||||
export let selectedDomain: Models.Domain;
|
||||
|
||||
let error = '';
|
||||
|
||||
async function deleteDomain() {
|
||||
try {
|
||||
await sdk.forProject.proxy.deleteRule(selectedDomain.$id);
|
||||
await sdk.forConsole.domains.delete(selectedDomain.$id);
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
show = false;
|
||||
addNotification({
|
||||
@@ -30,21 +29,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Confirm title="Delete domain" bind:open={show} onSubmit={deleteDomain} bind:error>
|
||||
{#if selectedDomain}
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>{selectedDomain.domain}</b>? You will no longer be
|
||||
able to execute your function by visiting this domain.
|
||||
</p>
|
||||
{/if}
|
||||
<InputCheckbox
|
||||
required
|
||||
bind:checked={confirm}
|
||||
id="confirm"
|
||||
label="I understand and confirm"
|
||||
size="s" />
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary submit disabled={!confirm}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
<Confirm onSubmit={deleteDomain} title="Delete domain" bind:open={show} bind:error>
|
||||
<p data-private>Are you sure you want to delete <b>{selectedDomain?.domain}</b>?</p>
|
||||
<p>Your site will no longer be available at this domain. This action is irreversible.</p>
|
||||
</Confirm>
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
export const load = async ({ depends, params }) => {
|
||||
depends(Dependencies.SITE);
|
||||
depends(Dependencies.DOMAIN);
|
||||
|
||||
return {
|
||||
domain: await sdk.forProject.proxy.getRule(params.domain),
|
||||
domain: await sdk.forConsole.domains.get(params.domain),
|
||||
header: Header,
|
||||
breadcrumbs: Breadcrumbs
|
||||
};
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@
|
||||
<DomainMetrics domain={data.domain} />
|
||||
|
||||
<Layout.Stack gap="l">
|
||||
{#if data.records.total}
|
||||
{#if data.recordList.total}
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Layout.Stack direction="row" gap="s" inline>
|
||||
<Button secondary>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Dependencies } from '$lib/constants';
|
||||
import { pageToOffset } from '$lib/helpers/load';
|
||||
import { getLimit } from '$lib/helpers/load';
|
||||
import { getPage } from '$lib/helpers/load';
|
||||
import { sdk } from '$lib/stores/sdk.js';
|
||||
|
||||
export const load = async ({ parent, depends, url, route }) => {
|
||||
depends(Dependencies.DOMAIN);
|
||||
@@ -14,47 +15,8 @@ export const load = async ({ parent, depends, url, route }) => {
|
||||
|
||||
return {
|
||||
domain,
|
||||
records: mockDNSRecords,
|
||||
recordList: await sdk.forConsole.domains.listRecords(domain.$id),
|
||||
offset,
|
||||
limit
|
||||
};
|
||||
};
|
||||
|
||||
const mockDNSRecords = {
|
||||
total: 4,
|
||||
records: [
|
||||
{
|
||||
type: 'A',
|
||||
name: '@',
|
||||
value: '192.0.2.1',
|
||||
ttl: 3600,
|
||||
proxied: true,
|
||||
$createdAt: '2023-01-15T10:30:00Z'
|
||||
},
|
||||
{
|
||||
type: 'CNAME',
|
||||
name: 'www',
|
||||
value: 'example.com',
|
||||
ttl: 3600,
|
||||
proxied: true,
|
||||
$createdAt: '2023-01-15T10:30:00Z'
|
||||
},
|
||||
{
|
||||
type: 'MX',
|
||||
name: '@',
|
||||
value: 'mail.example.com',
|
||||
priority: 10,
|
||||
ttl: 3600,
|
||||
proxied: false,
|
||||
$createdAt: '2023-01-15T10:30:00Z'
|
||||
},
|
||||
{
|
||||
type: 'TXT',
|
||||
name: '@',
|
||||
value: 'v=spf1 include:_spf.example.com ~all',
|
||||
ttl: 3600,
|
||||
proxied: false,
|
||||
$createdAt: '2023-01-15T10:30:00Z'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
-1
@@ -4,7 +4,6 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Skeleton, Table } from '@appwrite.io/pink-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
|
||||
export let show = false;
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
import { PAGE_LIMIT, Dependencies } from '$lib/constants';
|
||||
import { getSearch, pageToOffset, getLimit, getPage } from '$lib/helpers/load';
|
||||
import { sdk } from '$lib/stores/sdk.js';
|
||||
|
||||
export const load = async ({ parent, depends, url, route }) => {
|
||||
depends(Dependencies.DOMAINS);
|
||||
depends(Dependencies.DOMAIN);
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, PAGE_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
+2
-10
@@ -1,11 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Confirm } from '$lib/components';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show = false;
|
||||
export let selectedCertificate;
|
||||
@@ -14,8 +12,8 @@
|
||||
|
||||
async function deleteCertificate() {
|
||||
try {
|
||||
await invalidate(Dependencies.DOMAIN);
|
||||
//TODO: add method
|
||||
await invalidate(Dependencies.DOMAIN);
|
||||
show = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -30,11 +28,5 @@
|
||||
</script>
|
||||
|
||||
<Confirm title="Delete certificate" bind:open={show} onSubmit={deleteCertificate} bind:error>
|
||||
<Typography.Text>
|
||||
Are you sure you want to delete SSL certificate? This action is irreversible.
|
||||
</Typography.Text>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary submit danger>Delete</Button>
|
||||
</svelte:fragment>
|
||||
Are you sure you want to delete this SSL certificate? This action is irreversible.
|
||||
</Confirm>
|
||||
|
||||
+5
-3
@@ -68,7 +68,7 @@
|
||||
}
|
||||
];
|
||||
let name: string;
|
||||
let type: RecordType;
|
||||
let type: RecordType = 'A';
|
||||
let value: string;
|
||||
let ttl = 3600;
|
||||
let priority: number;
|
||||
@@ -99,9 +99,10 @@
|
||||
<Layout.Stack gap="l">
|
||||
<InputText id="name" label="Name" placeholder="subdomain" bind:value={name} />
|
||||
<Layout.Stack gap="xs">
|
||||
<InputSelect {options} bind:value={type} id="type" label="Type" />
|
||||
<InputSelect {options} bind:value={type} id="type" label="Type" required />
|
||||
<Layout.Stack direction="row" gap="xs">
|
||||
<Icon icon={IconInfo} />
|
||||
{options.find((option) => option.value === type)?.helper}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -109,7 +110,8 @@
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} />
|
||||
<span slot="tooltip">
|
||||
{options.find((option) => option.value === type)?.helper}
|
||||
Enter the target or destination for this DNS record (e.g., IP address, hostname,
|
||||
or other required data)
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Confirm } from '$lib/components';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let show = false;
|
||||
export let selectedRecord: Models.DnsRecord;
|
||||
|
||||
let error = '';
|
||||
|
||||
async function deleteDomain() {
|
||||
try {
|
||||
await sdk.forConsole.domains.deleteRecord($page.params.domain, selectedRecord.$id);
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
show = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Record has been deleted'
|
||||
});
|
||||
trackEvent(Submit.DomainDelete);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.DomainDelete);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Confirm onSubmit={deleteDomain} title="Delete domain" bind:open={show} bind:error>
|
||||
<p>Are you sure you want to delete this record? This action is irreversible.</p>
|
||||
</Confirm>
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
|
||||
export let domain: Models.ProxyRule;
|
||||
export let domain: Models.Domain;
|
||||
let metrics = [
|
||||
{
|
||||
value: domain.domain,
|
||||
@@ -19,11 +19,11 @@
|
||||
description: 'Nameservers'
|
||||
},
|
||||
{
|
||||
value: domain?.expiresAt ? toLocaleDate(domain?.expiresAt) : '-',
|
||||
value: domain?.expiry ? toLocaleDate(domain?.expiry) : '-',
|
||||
description: 'Exipiry date'
|
||||
},
|
||||
{
|
||||
value: domain?.autoRenewals ? 'On' : 'off',
|
||||
value: domain?.autoRenewal ? 'On' : 'off',
|
||||
description: 'Auto renewal'
|
||||
},
|
||||
{
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Button,
|
||||
InputNumber,
|
||||
InputSelect,
|
||||
InputText,
|
||||
InputTextarea
|
||||
} from '$lib/elements/forms';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Icon, Layout, Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { deepClone } from '$lib/helpers/object';
|
||||
|
||||
// type RecordType = 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT' | 'NS' | 'SRV' | 'CAA' | 'PTR';
|
||||
|
||||
export let show = false;
|
||||
export let selectedRecord: Models.DnsRecord;
|
||||
const options = [
|
||||
{
|
||||
value: 'A',
|
||||
label: 'A ',
|
||||
helper: 'A records map a domain to an IPv4 address, allowing browsers to find your website by translating the domain name to an IP address.'
|
||||
},
|
||||
{
|
||||
value: 'AAAA',
|
||||
label: 'AAAA ',
|
||||
helper: 'AAAA records map a domain to an IPv6 address, providing the same function as A records but for IPv6-enabled devices.'
|
||||
},
|
||||
{
|
||||
value: 'CNAME',
|
||||
label: 'CNAME',
|
||||
helper: 'CNAME records alias one domain name to another, allowing you to point subdomains or other domain names to an existing domain.'
|
||||
},
|
||||
{
|
||||
value: 'MX',
|
||||
label: 'MX ',
|
||||
helper: 'MX records specify mail servers responsible for receiving emails for a domain, helping route email traffic to the correct mail server.'
|
||||
},
|
||||
{
|
||||
value: 'TXT',
|
||||
label: 'TXT',
|
||||
helper: 'TXT records store arbitrary text data in DNS, commonly used for verification purposes, such as domain ownership or email security settings.'
|
||||
},
|
||||
{
|
||||
value: 'NS',
|
||||
label: 'NS',
|
||||
helper: "NS records define the authoritative DNS servers for a domain, directing queries to the servers that manage the domain's DNS settings."
|
||||
},
|
||||
{
|
||||
value: 'SRV',
|
||||
label: 'SRV',
|
||||
helper: 'SRV records specify the location (hostname and port number) of servers for specific services, directing traffic to particular servers based on service types.'
|
||||
},
|
||||
{
|
||||
value: 'CAA',
|
||||
label: 'CAA',
|
||||
helper: 'CAA records specify which certificate authorities are allowed to issue SSL certificates for your domain, helping to prevent unauthorized certificate issuance.'
|
||||
},
|
||||
{
|
||||
value: 'HTTPS',
|
||||
label: 'HTTPS',
|
||||
helper: 'HTTPS records define which service or endpoint handles secure HTTPS traffic for your domain, typically used in SSL/TLS configurations.'
|
||||
},
|
||||
{
|
||||
value: 'ALIAS',
|
||||
label: 'ALIAS',
|
||||
helper: 'ALIAS records are similar to CNAMEs but can be used for the root domain, allowing you to point your domain to another domain or server.'
|
||||
}
|
||||
];
|
||||
let record = deepClone(selectedRecord);
|
||||
let error = '';
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
//TODO: update DNS record
|
||||
|
||||
show = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Record has been updated`
|
||||
});
|
||||
trackEvent(Submit.RecordUpdate);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.RecordUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
$: if (!show) {
|
||||
selectedRecord = null;
|
||||
}
|
||||
|
||||
$: isDisabled = !symmetricDifference(Object.values(record), Object.values(selectedRecord))
|
||||
?.length;
|
||||
</script>
|
||||
|
||||
<Modal title="Update DNS record" bind:show bind:error onSubmit={handleSubmit}>
|
||||
<span slot="description">
|
||||
DNS records map domain names to IP addresses or other resources.
|
||||
</span>
|
||||
<Layout.Stack gap="l">
|
||||
<InputText id="name" label="Name" placeholder="subdomain" bind:value={record.name} />
|
||||
<Layout.Stack gap="xs">
|
||||
<InputSelect {options} bind:value={record.type} id="type" label="Type" />
|
||||
<Layout.Stack direction="row" gap="xs">
|
||||
<Icon icon={IconInfo} />
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
<InputText id="value" label="Value" placeholder="76.75.21.21" bind:value={record.value}>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} />
|
||||
<span slot="tooltip">
|
||||
{options.find((option) => option.value === record.type)?.helper}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
<Layout.Stack direction="row" gap="l">
|
||||
<InputNumber id="ttl" label="TTL" placeholder="Enter number" bind:value={record.ttl}>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} />
|
||||
<span slot="tooltip">
|
||||
TTL defines how long DNS information is cached. Lower values update faster;
|
||||
higher values reduce server load.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputNumber>
|
||||
<InputNumber
|
||||
id="priority"
|
||||
label="Priority"
|
||||
placeholder="Enter number"
|
||||
bind:value={record.priority}>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} />
|
||||
<span slot="tooltip">
|
||||
Sets the priority for this DNS record. Lower numbers indicate higher
|
||||
priority (e.g., 10 is higher than 20).
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputNumber>
|
||||
</Layout.Stack>
|
||||
|
||||
<InputTextarea
|
||||
id="comment"
|
||||
label="Comment"
|
||||
placeholder="Provide an explanation of this DNS record's purpose"
|
||||
bind:value={record.comment} />
|
||||
</Layout.Stack>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary submit disabled={isDisabled}>Update DNS record</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
+6
-2
@@ -3,14 +3,17 @@
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Button, InputSelect } from '$lib/elements/forms';
|
||||
import type { OrganizationList } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let domain: Models.ProxyRule;
|
||||
export let domain: Models.Domain;
|
||||
export let organizations: OrganizationList;
|
||||
let selectedOrg: string = null;
|
||||
|
||||
async function moveDomain() {
|
||||
console.log(selectedOrg);
|
||||
try {
|
||||
await sdk.forConsole.domains.updateTeam(domain.$id, selectedOrg);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
$: options = organizations.total
|
||||
@@ -27,6 +30,7 @@
|
||||
<InputSelect
|
||||
id="orgs"
|
||||
label="Move to"
|
||||
required
|
||||
placeholder="Select destination"
|
||||
disabled={options?.length === 0}
|
||||
{options}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import Delete from '../../deleteDomainModal.svelte';
|
||||
|
||||
export let domain: Models.ProxyRule;
|
||||
export let domain: Models.Domain;
|
||||
let show = false;
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ import type { Column } from '$lib/helpers/types';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
|
||||
export const domain = derived(page, ($page) => $page.data.domain as Models.ProxyRule);
|
||||
// columns: namem type, value, ttl, priority, comment, created
|
||||
export const domain = derived(page, ($page) => $page.data.domain as Models.Domain);
|
||||
export const columns = writable<Column[]>([
|
||||
{ id: 'name', title: 'Name', type: 'string', show: true },
|
||||
{ id: 'type', title: 'Type', type: 'string', show: true },
|
||||
|
||||
+20
-8
@@ -3,6 +3,7 @@
|
||||
|
||||
import {
|
||||
ActionMenu,
|
||||
Button,
|
||||
HiddenText,
|
||||
Icon,
|
||||
Layout,
|
||||
@@ -13,13 +14,16 @@
|
||||
import { IconDotsHorizontal, IconPencil, IconTrash } from '@appwrite.io/pink-icons-svelte';
|
||||
import { columns } from './store';
|
||||
import { timeFromNow } from '$lib/helpers/date';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import DeleteRecordModal from './deleteRecordModal.svelte';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import EditRecordModal from './editRecordModal.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
let showEdit = false;
|
||||
let showDelete = false;
|
||||
let selectedRecord; //TODO: add type
|
||||
let selectedRecord: Models.DnsRecord = null;
|
||||
</script>
|
||||
|
||||
<Layout.Stack>
|
||||
@@ -35,7 +39,7 @@
|
||||
<Table.Header.Cell width="40" />
|
||||
</svelte:fragment>
|
||||
|
||||
{#each data.records.records as record}
|
||||
{#each data.recordList.dnsRecords as record}
|
||||
<Table.Row>
|
||||
{#each $columns as column}
|
||||
{#if column.show}
|
||||
@@ -78,7 +82,7 @@
|
||||
{:else if column.id === '$createdAt'}
|
||||
<Table.Cell>
|
||||
<Typography.Text>
|
||||
{timeFromNow(record.$createdAt)}
|
||||
{capitalize(timeFromNow(record.$createdAt))}
|
||||
</Typography.Text>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
@@ -87,15 +91,16 @@
|
||||
<Table.Cell>
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<Popover let:toggle placement="bottom-start" padding="none">
|
||||
<Button
|
||||
text
|
||||
<Button.Button
|
||||
variant="text"
|
||||
icon
|
||||
size="s"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
toggle(e);
|
||||
}}>
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
</Button.Button>
|
||||
|
||||
<svelte:fragment slot="tooltip" let:toggle>
|
||||
<ActionMenu.Root>
|
||||
@@ -133,5 +138,12 @@
|
||||
name="Domains"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
total={data.records.total} />
|
||||
total={data.recordList.total} />
|
||||
</Layout.Stack>
|
||||
|
||||
{#if showDelete}
|
||||
<DeleteRecordModal bind:show={showDelete} {selectedRecord} />
|
||||
{/if}
|
||||
{#if showEdit}
|
||||
<EditRecordModal bind:show={showEdit} {selectedRecord} />
|
||||
{/if}
|
||||
|
||||
@@ -54,7 +54,8 @@
|
||||
{
|
||||
href: `${path}/domains`,
|
||||
event: 'domains',
|
||||
title: 'Domains'
|
||||
title: 'Domains',
|
||||
disabled: !isCloud
|
||||
// disabled: !(isCloud && $canSeeDomains) //TODO: enable this before release
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,9 +14,24 @@
|
||||
import { isOwner } from '$lib/stores/roles';
|
||||
import Edit from './edit.svelte';
|
||||
import { getRoleLabel } from '$lib/stores/billing';
|
||||
import { Drop } from '$lib/components';
|
||||
import { IconDotsHorizontal, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Layout, Table, Badge, Button, Icon } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconDotsHorizontal,
|
||||
IconInfo,
|
||||
IconPencil,
|
||||
IconPlus,
|
||||
IconRefresh,
|
||||
IconTrash
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import {
|
||||
Layout,
|
||||
Table,
|
||||
Badge,
|
||||
Button,
|
||||
Icon,
|
||||
Typography,
|
||||
Popover,
|
||||
ActionMenu
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import Upgrade from '$lib/components/roles/upgrade.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
@@ -25,7 +40,6 @@
|
||||
let showDelete = false;
|
||||
let showEdit = false;
|
||||
let showDropdown = [];
|
||||
let showPopover = false;
|
||||
|
||||
const resend = async (member: Models.Membership) => {
|
||||
try {
|
||||
@@ -54,7 +68,8 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Typography.Title>Members</Typography.Title>
|
||||
<ConsoleButton on:mousedown={() => newMemberModal.set(true)} event="create_user" size="s">
|
||||
<Icon size="s" icon={IconPlus} slot="start" />
|
||||
<span class="text">Invite</span>
|
||||
@@ -65,28 +80,16 @@
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell>Email</Table.Header.Cell>
|
||||
<Table.Header.Cell
|
||||
>Roles
|
||||
<Drop isPopover bind:show={showPopover} display="inline-block">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => (showPopover = !showPopover)}
|
||||
aria-label="input tooltip">
|
||||
<span
|
||||
class="icon-info"
|
||||
aria-hidden="true"
|
||||
style="font-size: var(--icon-size-small)" />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<div
|
||||
class="dropped card u-max-width-300 u-break-word"
|
||||
style:--card-border-radius="var(--border-radius-small)"
|
||||
style:--p-card-padding=".75rem"
|
||||
style:box-shadow="var(--shadow-large)">
|
||||
<svelte:component this={Upgrade} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Drop>
|
||||
<Table.Header.Cell>
|
||||
<Layout.Stack direction="row" gap="xxxs" alignItems="center">
|
||||
Roles
|
||||
<Popover let:toggle>
|
||||
<Button.Button icon variant="extra-compact" size="s" on:click={toggle}>
|
||||
<Icon size="s" icon={IconInfo} />
|
||||
</Button.Button>
|
||||
<svelte:component this={Upgrade} slot="tooltip" />
|
||||
</Popover>
|
||||
</Layout.Stack>
|
||||
</Table.Header.Cell>
|
||||
<Table.Header.Cell>2FA</Table.Header.Cell>
|
||||
{#if $isOwner}
|
||||
@@ -102,7 +105,7 @@
|
||||
{member.userName ? member.userName : 'n/a'}
|
||||
</span>
|
||||
{#if member.invited && !member.joined}
|
||||
<Badge type="warning" variant="secondary" content="Pending" />
|
||||
<Badge type="warning" variant="secondary" content="Pending" size="xs" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
@@ -121,49 +124,41 @@
|
||||
</Table.Cell>
|
||||
{#if $isOwner}
|
||||
<Table.Cell>
|
||||
<DropList bind:show={showDropdown[index]} placement="bottom-end" noArrow>
|
||||
<Button.Button
|
||||
icon
|
||||
variant="text"
|
||||
size="s"
|
||||
aria-label="More options"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
showDropdown[index] = !showDropdown[index];
|
||||
}}>
|
||||
<Icon icon={IconDotsHorizontal} size="m" />
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
<Button.Button icon variant="ghost" size="s" on:click={toggle}>
|
||||
<Icon size="s" icon={IconDotsHorizontal} />
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem
|
||||
icon="pencil"
|
||||
on:click={() => {
|
||||
selectedMember = member;
|
||||
showEdit = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Edit role
|
||||
</DropListItem>
|
||||
{#if member.invited && !member.joined}
|
||||
<DropListItem
|
||||
icon="refresh"
|
||||
<div style:min-width="232px" slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
trailingIcon={IconPencil}
|
||||
on:click={() => {
|
||||
resend(member);
|
||||
selectedMember = member;
|
||||
showEdit = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Resend
|
||||
</DropListItem>
|
||||
{/if}
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
on:click={() => {
|
||||
selectedMember = member;
|
||||
showDelete = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Remove
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
Edit role
|
||||
</ActionMenu.Item.Button>
|
||||
{#if member.invited && !member.joined}
|
||||
<ActionMenu.Item.Button
|
||||
trailingIcon={IconRefresh}
|
||||
on:click={() => {
|
||||
resend(member);
|
||||
}}>
|
||||
Resend
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
<ActionMenu.Item.Button
|
||||
trailingIcon={IconTrash}
|
||||
on:click={() => {
|
||||
selectedMember = member;
|
||||
showDelete = true;
|
||||
}}>
|
||||
Remove
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
</Popover>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
</Table.Row>
|
||||
|
||||
@@ -53,14 +53,13 @@
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Name</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={name} />
|
||||
</ul>
|
||||
<InputText
|
||||
required
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={name} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
+94
-107
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Alert, Modal, SecondaryTabs, SecondaryTabsItem } from '$lib/components';
|
||||
import { Button, FormList, InputText } from '$lib/elements/forms';
|
||||
import { Modal, SecondaryTabs, SecondaryTabsItem } from '$lib/components';
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { members, organization, organizationList } from '$lib/stores/organization';
|
||||
@@ -22,6 +22,7 @@
|
||||
} from '$lib/elements/table';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { tierToPlan } from '$lib/stores/billing';
|
||||
import { Table, Tabs, Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let showDelete = false;
|
||||
export let invoices: InvoiceList;
|
||||
@@ -102,116 +103,102 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="max-height-dialog">
|
||||
<Modal title="Delete organization" onSubmit={deleteOrg} bind:show={showDelete} bind:error>
|
||||
{#if upcomingInvoice}
|
||||
<Alert type="warning">
|
||||
<span slot="title">
|
||||
You have a pending {formatCurrency(upcomingInvoice.grossAmount)} invoice for your
|
||||
{tierToPlan(upcomingInvoice.plan).name} plan
|
||||
</span>
|
||||
<p>
|
||||
By proceeding, your invoice will be processed within the hour. Upon successful
|
||||
payment, your organization will be deleted.
|
||||
</p>
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<p data-private>
|
||||
{#if $projects.total > 0}
|
||||
The following projects and all data associated with <b>{$organization.name}</b> will
|
||||
be permanently deleted. <b>This action is irreversible</b>.
|
||||
{:else}
|
||||
All data associated with <b>{$organization.name}</b> will be permanently deleted.
|
||||
<b>This action is irreversible</b>.
|
||||
{/if}
|
||||
</p>
|
||||
<Modal title="Delete organization" onSubmit={deleteOrg} bind:show={showDelete} bind:error>
|
||||
{#if upcomingInvoice}
|
||||
<Alert.Inline
|
||||
status="warning"
|
||||
title={`You have a pending ${formatCurrency(upcomingInvoice.grossAmount)} invoice for your
|
||||
${tierToPlan(upcomingInvoice.plan).name} plan`}>
|
||||
By proceeding, your invoice will be processed within the hour. Upon successful payment,
|
||||
your organization will be deleted.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
|
||||
<p slot="description">
|
||||
{#if $projects.total > 0}
|
||||
<div class="box is-only-desktop">
|
||||
<SecondaryTabs large stretch class="u-sep-block-end u-padding-8">
|
||||
{#each tabs as { name, label, total }}
|
||||
<SecondaryTabsItem
|
||||
center
|
||||
fullWidth
|
||||
disabled={selectedTab === name}
|
||||
on:click={() => (selectedTab = name)}>
|
||||
{label.desktop} ({total})
|
||||
</SecondaryTabsItem>
|
||||
{/each}
|
||||
</SecondaryTabs>
|
||||
|
||||
<TableScroll dense noMargin>
|
||||
<TableHeader>
|
||||
{#each tabData.headers as header}
|
||||
<TableCellHead width={columnWidth}>{header}</TableCellHead>
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each tabData.rows as row}
|
||||
<TableRow>
|
||||
{#each row.cells as cell}
|
||||
<TableCell width={columnWidth}>{cell}</TableCell>
|
||||
{/each}
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
</div>
|
||||
<div class="box is-not-desktop">
|
||||
<SecondaryTabs large stretch class="u-sep-block-end u-padding-8">
|
||||
{#each tabs as { name, label, total }}
|
||||
<SecondaryTabsItem
|
||||
center
|
||||
fullWidth
|
||||
disabled={selectedTab === name}
|
||||
on:click={() => (selectedTab = name)}>
|
||||
{label.mobile} ({total})
|
||||
</SecondaryTabsItem>
|
||||
{/each}
|
||||
</SecondaryTabs>
|
||||
|
||||
<TableScroll dense noMargin>
|
||||
<TableHeader>
|
||||
{#each tabData.headers as header, index}
|
||||
<TableCellHead width={index === 1 ? columnWidthSmall : columnWidth}
|
||||
>{header}</TableCellHead>
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each tabData.rows as row}
|
||||
<TableRow>
|
||||
{#each row.cells as cell, index}
|
||||
<TableCell width={index === 1 ? columnWidthSmall : columnWidth}
|
||||
>{cell}</TableCell>
|
||||
{/each}
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
</div>
|
||||
The following projects and all data associated with <b>{$organization.name}</b> will be
|
||||
permanently deleted. <b>This action is irreversible</b>.
|
||||
{:else}
|
||||
All data associated with <b>{$organization.name}</b> will be permanently deleted.
|
||||
<b>This action is irreversible</b>.
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<FormList>
|
||||
<InputText
|
||||
label={`Confirm the organization name to continue`}
|
||||
placeholder="Enter {$organization.name} to continue"
|
||||
id="organization-name"
|
||||
required
|
||||
bind:value={organizationName} />
|
||||
</FormList>
|
||||
{#if $projects.total > 0}
|
||||
<Tabs.Root let:root stretch>
|
||||
{#each tabs as { name, label, total }}
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
on:click={() => (selectedTab = name)}
|
||||
active={selectedTab === name}>
|
||||
{label.desktop} ({total})
|
||||
</Tabs.Item.Button>
|
||||
{/each}
|
||||
</Tabs.Root>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button
|
||||
secondary
|
||||
submit
|
||||
disabled={!error && (!organizationName || organizationName !== $organization.name)}>
|
||||
Delete
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</div>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
{#each tabData.headers as header}
|
||||
<Table.Header.Cell width={columnWidth + 'px'}>{header}</Table.Header.Cell>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
{#each tabData.rows as row}
|
||||
<Table.Row>
|
||||
{#each row.cells as cell}
|
||||
<Table.Cell>{cell}</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
<div class="box is-not-desktop">
|
||||
<SecondaryTabs large stretch class="u-sep-block-end u-padding-8">
|
||||
{#each tabs as { name, label, total }}
|
||||
<SecondaryTabsItem
|
||||
center
|
||||
fullWidth
|
||||
disabled={selectedTab === name}
|
||||
on:click={() => (selectedTab = name)}>
|
||||
{label.mobile} ({total})
|
||||
</SecondaryTabsItem>
|
||||
{/each}
|
||||
</SecondaryTabs>
|
||||
|
||||
<TableScroll dense noMargin>
|
||||
<TableHeader>
|
||||
{#each tabData.headers as header, index}
|
||||
<TableCellHead width={index === 1 ? columnWidthSmall : columnWidth}
|
||||
>{header}</TableCellHead>
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each tabData.rows as row}
|
||||
<TableRow>
|
||||
{#each row.cells as cell, index}
|
||||
<TableCell width={index === 1 ? columnWidthSmall : columnWidth}
|
||||
>{cell}</TableCell>
|
||||
{/each}
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
</div>
|
||||
{/if}
|
||||
<InputText
|
||||
label={`Confirm the organization name to continue`}
|
||||
placeholder="Enter {$organization.name} to continue"
|
||||
id="organization-name"
|
||||
required
|
||||
bind:value={organizationName} />
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button
|
||||
secondary
|
||||
submit
|
||||
disabled={!error && (!organizationName || organizationName !== $organization.name)}>
|
||||
Delete
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.box {
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<Table.Cell width={column.width + 'px'}>
|
||||
<Copy value={user.$id} event="user">
|
||||
<Tag size="xs" variant="code">
|
||||
<Icon size="s" icon={IconDuplicate} />
|
||||
<Icon size="s" icon={IconDuplicate} slot="start" />
|
||||
{user.$id}
|
||||
</Tag>
|
||||
</Copy>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, Helper, InputTags } from '$lib/elements/forms';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
|
||||
+6
-26
@@ -28,8 +28,6 @@
|
||||
const projectId = $page.params.project;
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let showCreateDropdown = false;
|
||||
let showEmptyCreateDropdown = false;
|
||||
let showDropdown = [];
|
||||
let selectedOption: Option['name'] = null;
|
||||
let selectedAttribute: Attributes = null;
|
||||
@@ -53,7 +51,7 @@
|
||||
<Heading tag="h2" size="5">Attributes</Heading>
|
||||
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown bind:showCreateDropdown bind:selectedOption bind:showCreate />
|
||||
<CreateAttributeDropdown bind:selectedOption bind:showCreate />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -197,18 +195,8 @@
|
||||
<p class="text">Total results: {$attributes.length}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty
|
||||
allowCreate={$canWriteCollections}
|
||||
single
|
||||
target="attribute"
|
||||
on:click={() => (showEmptyCreateDropdown = true)}>
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h2">Create your first attribute to get started.</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
<Empty single target="attribute">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/databases/collections#attributes"
|
||||
@@ -216,21 +204,13 @@
|
||||
event="empty_documentation"
|
||||
ariaLabel={`create {target}`}>Documentation</Button>
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown
|
||||
bind:showCreateDropdown={showEmptyCreateDropdown}
|
||||
bind:selectedOption
|
||||
bind:showCreate>
|
||||
<Button
|
||||
secondary
|
||||
event="create_attribute"
|
||||
on:click={() => {
|
||||
showEmptyCreateDropdown = !showEmptyCreateDropdown;
|
||||
}}>
|
||||
<CreateAttributeDropdown bind:selectedOption bind:showCreate let:toggle>
|
||||
<Button secondary event="create_attribute" on:click={toggle}>
|
||||
Create attribute
|
||||
</Button>
|
||||
</CreateAttributeDropdown>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
+10
-16
@@ -1,36 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { DropList, DropListItem } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { ActionMenu, Icon, Popover } from '@appwrite.io/pink-svelte';
|
||||
import { attributeOptions, type Option } from './store';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let showCreateDropdown = false;
|
||||
|
||||
export let selectedOption: Option['name'] = null;
|
||||
export let showCreate = false;
|
||||
</script>
|
||||
|
||||
<DropList bind:show={showCreateDropdown} scrollable>
|
||||
<slot>
|
||||
<Button
|
||||
on:click={() => (showCreateDropdown = !showCreateDropdown)}
|
||||
event="create_attribute">
|
||||
<Popover let:toggle padding="none" placement="bottom-start">
|
||||
<slot {toggle}>
|
||||
<Button on:click={toggle} event="create_attribute">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create attribute
|
||||
</Button>
|
||||
</slot>
|
||||
<svelte:fragment slot="list">
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
{#each attributeOptions as attribute}
|
||||
<DropListItem
|
||||
icon={attribute.icon}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={attribute.icon}
|
||||
on:click={() => {
|
||||
selectedOption = attribute.name;
|
||||
showCreateDropdown = false;
|
||||
showCreate = true;
|
||||
}}>
|
||||
{attribute.name}
|
||||
</DropListItem>
|
||||
</ActionMenu.Item.Button>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
|
||||
@@ -14,6 +14,7 @@ export const templateConfig = writable<{
|
||||
repositoryId: string;
|
||||
execute?: boolean;
|
||||
scopes?: string[];
|
||||
specification?: string;
|
||||
}>();
|
||||
|
||||
export const choices = writable<{
|
||||
|
||||
+1
-4
@@ -49,10 +49,7 @@
|
||||
$templateConfig.repositoryBehaviour === 'manual'
|
||||
? undefined
|
||||
: $choices.rootDir || undefined,
|
||||
$template.providerRepositoryId || undefined,
|
||||
$template.providerOwner || undefined,
|
||||
runtimeDetail.providerRootDirectory || undefined,
|
||||
$template.providerVersion || undefined
|
||||
$templateConfig.specification
|
||||
);
|
||||
|
||||
if ($templateConfig.variables) {
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { DropList, DropListItem } from '$lib/components';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { targetsById } from './wizard/store';
|
||||
import UserTargetsModal from './userTargetsModal.svelte';
|
||||
import TopicsModal from './topicsModal.svelte';
|
||||
import { topicsById } from './store';
|
||||
import type { MessagingProviderType } from '@appwrite.io/console';
|
||||
import { ActionMenu, Popover } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let showDropdown: boolean;
|
||||
export let showUserTargets: boolean;
|
||||
export let showTopics: boolean;
|
||||
export let providerType: MessagingProviderType = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: if (showUserTargets || showTopics) {
|
||||
showDropdown = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropList bind:show={showDropdown} placement="bottom-end" fixed>
|
||||
<slot />
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => (showTopics = true)}>Select topics</DropListItem>
|
||||
<DropListItem on:click={() => (showUserTargets = true)}>Select targets</DropListItem>
|
||||
<Popover let:toggle placement="bottom-end">
|
||||
<slot {toggle} />
|
||||
<svelte:fragment slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button on:click={() => (showTopics = true)}
|
||||
>Select topics</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => (showUserTargets = true)}
|
||||
>Select targets</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</Popover>
|
||||
|
||||
<TopicsModal
|
||||
{providerType}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let showDropdown = false;
|
||||
let showTopics = false;
|
||||
let showUserTargets = false;
|
||||
let targetIdsLength = 0;
|
||||
@@ -65,12 +64,12 @@
|
||||
<div class="u-flex u-cross-center u-flex-vertical u-main-center u-flex">
|
||||
<Actions
|
||||
providerType={$providerType}
|
||||
bind:showDropdown
|
||||
bind:showUserTargets
|
||||
bind:showTopics
|
||||
on:addTargets={addTargets}
|
||||
on:addTopics={addTopics}>
|
||||
<Button secondary icon on:click={() => (showDropdown = !showDropdown)}>
|
||||
on:addTopics={addTopics}
|
||||
let:toggle>
|
||||
<Button secondary icon on:click={toggle}>
|
||||
<Icon icon={IconPlus} size="s" />
|
||||
</Button>
|
||||
</Actions>
|
||||
@@ -143,12 +142,12 @@
|
||||
</div>
|
||||
<Actions
|
||||
providerType={$providerType}
|
||||
bind:showDropdown
|
||||
bind:showUserTargets
|
||||
bind:showTopics
|
||||
on:addTargets={addTargets}
|
||||
on:addTopics={addTopics}>
|
||||
<Button text on:click={() => (showDropdown = !showDropdown)}>
|
||||
on:addTopics={addTopics}
|
||||
let:toggle>
|
||||
<Button text on:click={toggle}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add
|
||||
</Button>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { afterNavigate, goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
@@ -15,10 +15,7 @@
|
||||
import { humanFileSize } from '$lib/helpers/sizeConvertion';
|
||||
import { Container, type UsagePeriods } from '$lib/layout';
|
||||
import { onMount, setContext, SvelteComponent } from 'svelte';
|
||||
import { project } from '../store';
|
||||
import Bandwidth from './bandwidth.svelte';
|
||||
import { createApiKey } from './keys/+page.svelte';
|
||||
import Onboard from './onboard.svelte';
|
||||
import Realtime from './realtime.svelte';
|
||||
import Requests from './requests.svelte';
|
||||
import { usage } from './store';
|
||||
@@ -27,8 +24,7 @@
|
||||
import type { Metric } from '$lib/sdk/usage';
|
||||
import { periodToDates } from '$lib/layout/usage.svelte';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import { hasOnboardingDismissed } from '$lib/helpers/onboarding';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Card, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
@@ -70,7 +66,7 @@
|
||||
label: 'Create API Key',
|
||||
icon: IconPlus,
|
||||
callback() {
|
||||
createApiKey();
|
||||
goto(`${base}/project-[project]/overview/keys/create`);
|
||||
},
|
||||
keys: ['c', 'k'],
|
||||
group: 'integrations',
|
||||
@@ -87,21 +83,27 @@
|
||||
<title>Console - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if $project}
|
||||
<Container overlapCover>
|
||||
<Container overlapCover>
|
||||
<!-- TODO: fix with nicer solution -->
|
||||
<div class="flex-container">
|
||||
{#if $usage}
|
||||
{@const storage = humanFileSize($usage.filesStorageTotal ?? 0)}
|
||||
<section class="common-section">
|
||||
<div class="grid-dashboard-1s-2m-6l">
|
||||
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
|
||||
<div class="grid-dashboard-1s-2m-6l" style:gap="1rem">
|
||||
<Card.Base
|
||||
class="is-2-columns-medium-screen is-3-columns-large-screen"
|
||||
padding="s">
|
||||
<Bandwidth {period} on:change={(e) => changePeriod(e.detail)} />
|
||||
</div>
|
||||
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
|
||||
</Card.Base>
|
||||
<Card.Base
|
||||
class="is-2-columns-medium-screen is-3-columns-large-screen"
|
||||
padding="s">
|
||||
<Requests {period} on:change={(e) => changePeriod(e.detail)} />
|
||||
</div>
|
||||
<a
|
||||
</Card.Base>
|
||||
<Card.Link
|
||||
padding="s"
|
||||
href={`${base}/project-${projectId}/databases`}
|
||||
class="card is-2-columns-large-screen">
|
||||
class="is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
@@ -113,22 +115,17 @@
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
<Typography.Title>
|
||||
{formatNum($usage.documentsTotal ?? 0)}
|
||||
</div>
|
||||
<div>Documents</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text">
|
||||
Databases: {formatNum($usage.databasesTotal ?? 0)}
|
||||
</div>
|
||||
</Typography.Title>
|
||||
<Typography.Text>Documents</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
</Card.Link>
|
||||
<Card.Link
|
||||
padding="s"
|
||||
href={`${base}/project-${projectId}/storage`}
|
||||
class="card is-2-columns-large-screen">
|
||||
class="is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
@@ -140,23 +137,18 @@
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
<Typography.Title>
|
||||
{storage.value}
|
||||
<span class="body-text-2">{storage.unit}</span>
|
||||
</div>
|
||||
<div>Storage</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text">
|
||||
Buckets: {formatNum($usage.bucketsTotal ?? 0)}
|
||||
</div>
|
||||
</Typography.Title>
|
||||
<Typography.Text>Storage</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
</Card.Link>
|
||||
<Card.Link
|
||||
padding="s"
|
||||
href={`${base}/project-${projectId}/auth`}
|
||||
class="card is-2-columns-large-screen">
|
||||
class="is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
@@ -168,16 +160,17 @@
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
<Typography.Title>
|
||||
{formatNum($usage.usersTotal ?? 0)}
|
||||
</div>
|
||||
<div>Users</div>
|
||||
</Typography.Title>
|
||||
<Typography.Text>Users</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
</Card.Link>
|
||||
<Card.Link
|
||||
padding="s"
|
||||
href={`${base}/project-${projectId}/functions`}
|
||||
class="card is-2-columns-large-screen">
|
||||
class="is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
@@ -189,21 +182,22 @@
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
<Typography.Title>
|
||||
{formatNum($usage.executionsTotal ?? 0)}
|
||||
</div>
|
||||
<div>Executions</div>
|
||||
</Typography.Title>
|
||||
<Typography.Text>Executions</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
class="card is-2-columns-medium-screen is-2-columns-large-screen is-2-rows-large-screen is-location-row-2-end-large-screen">
|
||||
</Card.Link>
|
||||
<Card.Base
|
||||
padding="s"
|
||||
class="is-2-columns-medium-screen is-2-columns-large-screen is-2-rows-large-screen is-location-row-2-end-large-screen">
|
||||
<Realtime />
|
||||
</div>
|
||||
</Card.Base>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
@@ -213,10 +207,12 @@
|
||||
<Layout.Stack gap="xl" direction="row" justifyContent="space-between">
|
||||
<Tabs>
|
||||
<Tab
|
||||
noscroll
|
||||
href={`${path}/platforms`}
|
||||
selected={$page.url.pathname === `${path}/platforms`}
|
||||
event="platforms">Platforms</Tab>
|
||||
<Tab
|
||||
noscroll
|
||||
href={`${path}/keys`}
|
||||
selected={$page.url.pathname === `${path}/keys`}
|
||||
event="keys">API keys</Tab>
|
||||
@@ -227,5 +223,13 @@
|
||||
</Layout.Stack>
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
</Container>
|
||||
{/if}
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<style>
|
||||
.flex-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-12);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { Card, DropList, DropListItem } from '$lib/components';
|
||||
import { Card } from '$lib/components';
|
||||
import { humanFileSize } from '$lib/helpers/sizeConvertion';
|
||||
import { totalMetrics } from './+layout.svelte';
|
||||
import { usage } from './store';
|
||||
import type { UsagePeriods } from '$lib/layout';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { BarChart } from '$lib/charts';
|
||||
import {
|
||||
Popover,
|
||||
Typography,
|
||||
Icon,
|
||||
ActionMenu,
|
||||
Link,
|
||||
Layout,
|
||||
Button
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { IconChevronDown, IconChevronUp } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let period: UsagePeriods;
|
||||
|
||||
let showPeriod = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: network = $usage?.network as unknown as Array<{
|
||||
@@ -19,32 +27,33 @@
|
||||
}>;
|
||||
|
||||
$: bandwidth = humanFileSize(totalMetrics($usage?.network));
|
||||
|
||||
$: if (period) {
|
||||
showPeriod = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-gap-16 u-main-space-between">
|
||||
<Layout.Stack justifyContent="space-between" direction="row" alignItems="flex-start">
|
||||
<div>
|
||||
<div class="heading-level-4">
|
||||
<Typography.Title>
|
||||
{bandwidth.value}
|
||||
<span class="body-text-2">{bandwidth.unit}</span>
|
||||
</div>
|
||||
<div>Bandwidth</div>
|
||||
</Typography.Title>
|
||||
<Typography.Text>Bandwidth</Typography.Text>
|
||||
</div>
|
||||
<DropList bind:show={showPeriod} placement="bottom-start" childStart noArrow>
|
||||
<button class="transparent-button" on:click={() => (showPeriod = !showPeriod)}>
|
||||
<span class="text">{period}</span>
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => dispatch('change', '24h')}>24h</DropListItem>
|
||||
<DropListItem on:click={() => dispatch('change', '30d')}>30d</DropListItem>
|
||||
<DropListItem on:click={() => dispatch('change', '90d')}>90d</DropListItem>
|
||||
<Popover let:toggle padding="none" let:showing>
|
||||
<Button.Button on:click={toggle} variant="extra-compact">
|
||||
{period}
|
||||
<Icon icon={showing ? IconChevronUp : IconChevronDown} slot="end" />
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '24h')}
|
||||
>24h</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '30d')}
|
||||
>30d</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '90d')}
|
||||
>90d</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
</Popover>
|
||||
</Layout.Stack>
|
||||
{#if bandwidth.value !== '0'}
|
||||
<div style="height: 12rem;">
|
||||
<BarChart
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { user } from '$lib/stores/user';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
</script>
|
||||
|
||||
{#if !$page.url.pathname.includes('get-started')}
|
||||
@@ -45,6 +46,7 @@
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={async () => {
|
||||
trackEvent('onboarding_hub_platform_dismiss');
|
||||
await setHasOnboardingDismissed($project.$id);
|
||||
goto(`${base}/project-${$project.$id}/overview`);
|
||||
requestAnimationFrame(() => {
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
<script lang="ts" context="module">
|
||||
export function createApiKey() {
|
||||
if (!get(canWriteKeys)) {
|
||||
return;
|
||||
}
|
||||
wizard.start(Wizard);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Empty } from '$lib/components';
|
||||
import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { canWriteKeys } from '$lib/stores/roles';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { get } from 'svelte/store';
|
||||
import type { PageData } from './$types';
|
||||
import Wizard from './wizard.svelte';
|
||||
import Action from './action.svelte';
|
||||
import { setOverviewAction } from '../context';
|
||||
import { Table } from '@appwrite.io/pink-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -55,5 +46,5 @@
|
||||
allowCreate={$canWriteKeys}
|
||||
href="https://appwrite.io/docs/advanced/platform/api-keys"
|
||||
target="API key"
|
||||
on:click={createApiKey} />
|
||||
on:click={() => goto(`${base}/project-${$page.params.project}/overview/keys/create`)} />
|
||||
{/if}
|
||||
|
||||
+1
-3
@@ -62,9 +62,7 @@
|
||||
<p>Update the expiration date to keep the key active</p>
|
||||
</Alert>
|
||||
{/if}
|
||||
<FormList>
|
||||
<ExpirationInput bind:value={expiration} />
|
||||
</FormList>
|
||||
<ExpirationInput bind:value={expiration} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { canWriteKeys } from '$lib/stores/roles';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { createApiKey } from './+page.svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
{#if $canWriteKeys}
|
||||
<Button on:click={createApiKey}>
|
||||
<Button href={`${base}/project-${$page.params.project}/overview/keys/create`}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create API key
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { InputText } from '$lib/elements/forms/index.js';
|
||||
import { Wizard } from '$lib/layout';
|
||||
import { Fieldset, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import ExpirationInput from '../expirationInput.svelte';
|
||||
import Scopes from '../scopes.svelte';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import Form from '$lib/elements/forms/form.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onboarding } from '$routes/(console)/project-[project]/store';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
let showExitModal = false;
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
|
||||
let name = '',
|
||||
expire = '';
|
||||
let scopes = [];
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
const { $id } = await sdk.forConsole.projects.createKey(
|
||||
$page.params.project,
|
||||
name,
|
||||
scopes,
|
||||
expire || undefined
|
||||
);
|
||||
|
||||
if ($onboarding) {
|
||||
await invalidate(Dependencies.PROJECT);
|
||||
}
|
||||
trackEvent(Submit.KeyCreate, {
|
||||
scopes: scopes
|
||||
});
|
||||
goto(`${base}/project-${$page.params.project}/overview/keys/${$id}`);
|
||||
addNotification({
|
||||
message: `API key has been created`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.KeyCreate);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Wizard
|
||||
title="Create API key"
|
||||
href={`${base}/project-${$page.params.project}/overview/keys/`}
|
||||
bind:showExitModal
|
||||
column
|
||||
confirmExit>
|
||||
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Fieldset legend="Settings">
|
||||
<Layout.Stack>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="API key name"
|
||||
required
|
||||
bind:value={name} />
|
||||
<ExpirationInput bind:value={expire} />
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
|
||||
<Fieldset legend="Scopes">
|
||||
<Layout.Stack gap="xl">
|
||||
<Typography.Text>
|
||||
Choose which permission scopes to grant your application. It is best
|
||||
practice to allow only the permissions you need to meet your project goals.
|
||||
</Typography.Text>
|
||||
<Scopes bind:scopes />
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button fullWidthMobile secondary on:click={() => (showExitModal = true)}>Cancel</Button>
|
||||
<Button
|
||||
fullWidthMobile
|
||||
on:click={() => formComponent.triggerSubmit()}
|
||||
disabled={$isSubmitting}>
|
||||
Create
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Wizard>
|
||||
@@ -73,7 +73,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputSelect bind:value={expirationSelect} {options} id="preset" label="Expiration Date">
|
||||
<InputSelect bind:value={expirationSelect} {options} id="preset" label="Expiration date">
|
||||
<svelte:fragment slot="helper">
|
||||
{#if expirationSelect !== 'custom' && expirationSelect !== null}
|
||||
<Helper type="neutral">Your key will expire in {toLocaleDate(value)}</Helper>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import Checkbox from './checkbox.svelte';
|
||||
import { Accordion, Divider, Input, Layout, Selector } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let scopes: string[];
|
||||
|
||||
@@ -40,7 +41,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function categoryState(category: string, s: string[]): boolean | null {
|
||||
function categoryState(category: string, s: string[]): boolean | 'indeterminate' {
|
||||
const scopesByCategory = allScopes.filter((n) => n.category === category);
|
||||
const filtered = scopesByCategory.filter((n) => s.includes(n.scope));
|
||||
if (filtered.length === 0) {
|
||||
@@ -48,16 +49,15 @@
|
||||
} else if (filtered.length === scopesByCategory.length) {
|
||||
return true;
|
||||
} else {
|
||||
return null;
|
||||
return 'indeterminate';
|
||||
}
|
||||
}
|
||||
|
||||
function onCategoryChange(event: Event, category: Category) {
|
||||
function onCategoryChange(event: CustomEvent<boolean | 'indeterminate'>, category: Category) {
|
||||
if (event.detail === 'indeterminate') return;
|
||||
allScopes.forEach((s) => {
|
||||
if (s.category === category) {
|
||||
activeScopes[s.scope] = (
|
||||
event.currentTarget as EventTarget & HTMLInputElement
|
||||
).checked;
|
||||
activeScopes[s.scope] = event.detail;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -81,47 +81,39 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ul class="buttons-list u-main-end">
|
||||
<li class="buttons-list-item">
|
||||
<Button text on:click={deselectAll}>Deselect all</Button>
|
||||
</li>
|
||||
<li class="buttons-list-item">
|
||||
<Button text on:click={selectAll}>Select all</Button>
|
||||
</li>
|
||||
</ul>
|
||||
<Layout.Stack>
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Button compact on:click={selectAll}>Select all</Button>
|
||||
<span style:height="20px">
|
||||
<Divider vertical />
|
||||
</span>
|
||||
<Button compact on:click={deselectAll}>Deselect all</Button>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="none">
|
||||
<Divider />
|
||||
|
||||
<Collapsible>
|
||||
{#each [Category.Auth, Category.Database, Category.Functions, Category.Storage, Category.Messaging, Category.Other] as category}
|
||||
{@const checked = categoryState(category, scopes)}
|
||||
<CollapsibleItem withIndentation>
|
||||
<svelte:fragment slot="beforetitle">
|
||||
<Checkbox
|
||||
{checked}
|
||||
indeterminate={checked === null ? true : false}
|
||||
on:change={(e) => onCategoryChange(e, category)} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
{category}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
{@const scopesLength = allScopes.filter(
|
||||
(n) => n.category === category && scopes.includes(n.scope)
|
||||
).length}
|
||||
({scopesLength}
|
||||
{scopesLength === 1 ? 'Scope' : 'Scopes'})
|
||||
</svelte:fragment>
|
||||
<div class="form">
|
||||
<FormList>
|
||||
{#each [Category.Auth, Category.Database, Category.Functions, Category.Storage, Category.Messaging, Category.Other] as category}
|
||||
{@const checked = categoryState(category, scopes)}
|
||||
{@const scopesLength = allScopes.filter(
|
||||
(n) => n.category === category && scopes.includes(n.scope)
|
||||
).length}
|
||||
<Accordion
|
||||
selectable
|
||||
title={category}
|
||||
badge={`${scopesLength} ${scopesLength === 1 ? 'Scope' : 'Scopes'}`}
|
||||
{checked}
|
||||
on:change={(event) => onCategoryChange(event, category)}>
|
||||
<Layout.Stack>
|
||||
{#each allScopes.filter((s) => s.category === category) as scope}
|
||||
<InputChoice
|
||||
<Selector.Checkbox
|
||||
size="s"
|
||||
id={scope.scope}
|
||||
label={scope.scope}
|
||||
bind:value={activeScopes[scope.scope]}>
|
||||
{scope.description}
|
||||
</InputChoice>
|
||||
description={scope.description}
|
||||
bind:checked={activeScopes[scope.scope]} />
|
||||
{/each}
|
||||
</FormList>
|
||||
</div>
|
||||
</CollapsibleItem>
|
||||
{/each}
|
||||
</Collapsible>
|
||||
</Layout.Stack>
|
||||
</Accordion>
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { WizardWithSteps } from '$lib/layout';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import type { WizardStepsType } from '$lib/layout/wizardWithSteps.svelte';
|
||||
import Step1 from './wizard/step1.svelte';
|
||||
import Step2 from './wizard/step2.svelte';
|
||||
import { key } from './wizard/store';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { page } from '$app/stores';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { onboarding } from '../../store';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
async function onFinish() {
|
||||
try {
|
||||
const { $id } = await sdk.forConsole.projects.createKey(
|
||||
$page.params.project,
|
||||
$key.name,
|
||||
$key.scopes,
|
||||
$key.expire || undefined
|
||||
);
|
||||
if ($onboarding) {
|
||||
await invalidate(Dependencies.PROJECT);
|
||||
}
|
||||
trackEvent(Submit.KeyCreate, {
|
||||
scopes: $key.scopes
|
||||
});
|
||||
goto(`${base}/project-${$page.params.project}/overview/keys/${$id}`);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.KeyCreate);
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
key.reset();
|
||||
});
|
||||
|
||||
const stepsComponents: WizardStepsType = new Map();
|
||||
stepsComponents.set(1, {
|
||||
label: 'Details',
|
||||
component: Step1
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Scopes',
|
||||
component: Step2
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardWithSteps title="Create an API key" steps={stepsComponents} on:finish={onFinish} />
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { FormList, InputText } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import ExpirationInput from '../expirationInput.svelte';
|
||||
import { key } from './store';
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">API key</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">Let's create an API key.</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="API key name"
|
||||
required
|
||||
bind:value={$key.name} />
|
||||
<ExpirationInput bind:value={$key.expire} />
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
@@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { FormList } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import Scopes from '../scopes.svelte';
|
||||
import { key } from './store';
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Scopes</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Choose which permission scopes to grant your application. It is best practice to allow only
|
||||
the permissions you need to meet your project goals.
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
<Scopes bind:scopes={$key.scopes} />
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
@@ -1,25 +0,0 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
function createKeyStore() {
|
||||
const { subscribe, update, set } = writable<Partial<Models.Key>>({
|
||||
name: null,
|
||||
expire: null,
|
||||
scopes: []
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
update,
|
||||
set,
|
||||
reset() {
|
||||
set({
|
||||
name: null,
|
||||
expire: null,
|
||||
scopes: []
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const key = createKeyStore();
|
||||
@@ -31,15 +31,21 @@
|
||||
import PlatformFlutterImgSource from './assets/platform-flutter.svg';
|
||||
import PlatformFlutterImgSourceDark from './assets/platform-flutter-dark.svg';
|
||||
import { base } from '$app/paths';
|
||||
import Wizard from './keys/wizard.svelte';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { AvatarGroup } from '$lib/components';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { getPlatformInfo } from '$lib/helpers/platform';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
function createKey() {
|
||||
wizard.start(Wizard);
|
||||
trackEvent('onboarding_hub_platform', {
|
||||
platform: 'server',
|
||||
state: 'add'
|
||||
});
|
||||
goto(`${base}/project-${projectId}/overview/keys/create`, {
|
||||
replaceState: true
|
||||
});
|
||||
}
|
||||
|
||||
export let projectId: string;
|
||||
@@ -386,7 +392,12 @@
|
||||
<Layout.Stack
|
||||
gap="l"
|
||||
direction={$isSmallViewport ? 'column' : 'row'}>
|
||||
<Card.Button on:click={() => addPlatform(0)} padding="s"
|
||||
<Card.Button
|
||||
on:click={() => {
|
||||
trackEvent('onboarding_hub_setup_databases');
|
||||
goto(`${base}/project-${projectId}/databases`);
|
||||
}}
|
||||
padding="s"
|
||||
><Layout.Stack gap="xl"
|
||||
><div
|
||||
class="card-top-image database-card-image"
|
||||
@@ -419,20 +430,38 @@
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href="https://appwrite.io/docs/references"
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_api_references'
|
||||
);
|
||||
}}
|
||||
target="_blank">API references</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href="https://appwrite.io/docs/tutorials"
|
||||
on:click={() => {
|
||||
trackEvent('onboarding_hub_tutorials');
|
||||
}}
|
||||
target="_blank">Tutorials</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href="https://appwrite.io/docs/products/storage/quick-start"
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_storage_quick_start'
|
||||
);
|
||||
}}
|
||||
target="_blank"
|
||||
>Storage quick start
|
||||
</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href="https://appwrite.io/docs/products/functions/quick-start"
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_functions_quick_start'
|
||||
);
|
||||
}}
|
||||
target="_blank"
|
||||
>Functions quick start</Link.Anchor>
|
||||
</Layout.Stack>
|
||||
@@ -458,16 +487,29 @@
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href={`${base}/project-${projectId}/auth/settings`}
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_auth_email_password'
|
||||
);
|
||||
}}
|
||||
>E-mail and password
|
||||
</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href={`${base}/project-${projectId}/auth/settings`}
|
||||
>OAuth 2</Link.Anchor>
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_auth_oauth2'
|
||||
);
|
||||
}}>OAuth 2</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href={`${base}/project-${projectId}/auth/settings`}
|
||||
>View all methods</Link.Anchor>
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_auth_all_methods'
|
||||
);
|
||||
}}>View all methods</Link.Anchor>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<div
|
||||
@@ -481,7 +523,12 @@
|
||||
</div></Card.Base>
|
||||
</div>
|
||||
|
||||
<Card.Link href="https://appwrite.io/discord" padding="s">
|
||||
<Card.Link
|
||||
href="https://appwrite.io/discord"
|
||||
padding="s"
|
||||
on:click={() => {
|
||||
trackEvent('onboarding_hub_discord');
|
||||
}}>
|
||||
<div class="full-height-card">
|
||||
<Layout.Stack gap="xs" justifyContent="space-between">
|
||||
<Layout.Stack direction="column" gap="xxxs">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts" context="module">
|
||||
import SearchDark from '$lib/images/search-dark.svg';
|
||||
import SearchLight from '$lib/images/search-light.svg';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import CreateAndroid from './createAndroid.svelte';
|
||||
import CreateApple from './createApple.svelte';
|
||||
import CreateFlutter from './createFlutter.svelte';
|
||||
import CreateReactNative from './createReactNative.svelte';
|
||||
import CreateWeb from './createWeb.svelte';
|
||||
import { createPlatform, versions } from './wizard/store';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
export enum Platform {
|
||||
Web,
|
||||
@@ -19,6 +19,10 @@
|
||||
export async function addPlatform(type: Platform) {
|
||||
await versions.load();
|
||||
createPlatform.reset();
|
||||
trackEvent('onboarding_hub_platform', {
|
||||
platform: platforms[type],
|
||||
state: 'add'
|
||||
});
|
||||
wizard.start(platforms[type]);
|
||||
}
|
||||
|
||||
@@ -34,13 +38,19 @@
|
||||
key: key,
|
||||
type: type
|
||||
});
|
||||
trackEvent('onboarding_hub_platform', {
|
||||
platform: platforms[platform],
|
||||
state: 'continue'
|
||||
});
|
||||
wizard.start(platforms[platform], null, 1, {
|
||||
isPlatformCreated: true,
|
||||
platform: type
|
||||
platform: type,
|
||||
key: key
|
||||
});
|
||||
}
|
||||
|
||||
const platforms = {
|
||||
[Platform.Web]: CreateWeb,
|
||||
[Platform.Flutter]: CreateFlutter,
|
||||
[Platform.Android]: CreateAndroid,
|
||||
[Platform.Apple]: CreateApple,
|
||||
@@ -51,15 +61,29 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { DropList, DropListItem, Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { app } from '$lib/stores/app';
|
||||
import type { PageData } from './$types';
|
||||
import { canWritePlatforms } from '$lib/stores/roles';
|
||||
import { setOverviewAction } from '../context';
|
||||
import { Table } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
ActionMenu,
|
||||
Card,
|
||||
Empty,
|
||||
Icon,
|
||||
Layout,
|
||||
Popover,
|
||||
Table
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import Action from './action.svelte';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
IconCode,
|
||||
IconFlutter,
|
||||
IconReact
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import type { ComponentType } from 'svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -79,22 +103,21 @@
|
||||
'react-native-ios' = 'iOS',
|
||||
'web' = 'Web'
|
||||
}
|
||||
let showDropdownEmpty = false;
|
||||
const path = `${base}/project-${$page.params.project}/overview/platforms`;
|
||||
|
||||
const getPlatformInfo = (platform: string) => {
|
||||
function getPlatformInfo(platform: string): ComponentType {
|
||||
if (platform.includes('flutter')) {
|
||||
return 'color/flutter';
|
||||
return IconFlutter;
|
||||
} else if (platform.includes('react-native')) {
|
||||
return 'color/react';
|
||||
return IconReact;
|
||||
} else if (platform.includes('apple')) {
|
||||
return 'color/apple';
|
||||
return IconApple;
|
||||
} else if (platform.includes('android')) {
|
||||
return 'color/android';
|
||||
return IconAndroid;
|
||||
} else {
|
||||
return 'unknown';
|
||||
return IconCode;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
setOverviewAction(Action);
|
||||
</script>
|
||||
@@ -112,7 +135,10 @@
|
||||
{platform.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{PlatformTypes[platform.type]}
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center">
|
||||
<Icon icon={getPlatformInfo(platform.type)} />
|
||||
{PlatformTypes[platform.type]}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{platform.$updatedAt ? toLocaleDate(platform.$updatedAt) : 'never'}
|
||||
@@ -121,49 +147,49 @@
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{:else}
|
||||
<article class="card u-grid u-cross-center u-width-full-line common-section">
|
||||
<div class="u-flex u-flex-vertical u-cross-center u-gap-24">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img src={SearchDark} alt="empty" aria-hidden="true" />
|
||||
{:else}
|
||||
<img src={SearchLight} alt="empty" aria-hidden="true" />
|
||||
{/if}
|
||||
<slot>
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h4">Create a platform to get started.</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
<Button external href="https://appwrite.io/docs/sdks" text>
|
||||
Documentation
|
||||
</Button>
|
||||
<DropList bind:show={showDropdownEmpty} placement="bottom-start">
|
||||
{#if $canWritePlatforms}
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => (showDropdownEmpty = !showDropdownEmpty)}>
|
||||
<span class="text">Add platform</span>
|
||||
</Button>
|
||||
{/if}
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => addPlatform(Platform.Flutter)}>
|
||||
<Card.Base padding="none">
|
||||
<Empty
|
||||
title="Add a platform to get started."
|
||||
description="Need a hand? Learn more in our documentation.">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button external href="https://appwrite.io/docs/sdks" text>Documentation</Button>
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
{#if $canWritePlatforms}
|
||||
<Button secondary on:click={toggle}>
|
||||
<span class="text">Add platform</span>
|
||||
</Button>
|
||||
{/if}
|
||||
<svelte:fragment slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Web)}
|
||||
leadingIcon={IconCode}>
|
||||
Web
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Flutter)}
|
||||
leadingIcon={IconFlutter}>
|
||||
Flutter
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Android)}>
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Android)}
|
||||
leadingIcon={IconAndroid}>
|
||||
Android
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Apple)}>
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Apple)}
|
||||
leadingIcon={IconApple}>
|
||||
Apple
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.ReactNative)}>
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.ReactNative)}
|
||||
leadingIcon={IconReact}>
|
||||
React Native
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</article>
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
</Card.Base>
|
||||
{/if}
|
||||
|
||||
@@ -49,14 +49,12 @@
|
||||
<svelte:fragment slot="title">Package name</svelte:fragment>
|
||||
Your package name is generally the applicationId in your app-level build.gradle file.
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="key"
|
||||
label="Package Name"
|
||||
bind:value={key}
|
||||
required
|
||||
placeholder="com.company.appname" />
|
||||
</FormList>
|
||||
<InputText
|
||||
id="key"
|
||||
label="Package name"
|
||||
bind:value={key}
|
||||
required
|
||||
placeholder="com.company.appname" />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
+6
-8
@@ -49,14 +49,12 @@
|
||||
<svelte:fragment slot="title">Package name</svelte:fragment>
|
||||
Your package name is generally the applicationId in your app-level build.gradle file.
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="package-name"
|
||||
label="Package Name"
|
||||
bind:value={key}
|
||||
required
|
||||
placeholder="com.company.appname" />
|
||||
</FormList>
|
||||
<InputText
|
||||
id="package-name"
|
||||
label="Package name"
|
||||
bind:value={key}
|
||||
required
|
||||
placeholder="com.company.appname" />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
@@ -3,25 +3,56 @@
|
||||
import DropListItem from '$lib/components/dropListItem.svelte';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { canWritePlatforms } from '$lib/stores/roles';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { ActionMenu, Icon, Popover } from '@appwrite.io/pink-svelte';
|
||||
import { addPlatform, Platform } from './+page.svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
IconCode,
|
||||
IconFlutter,
|
||||
IconPlus,
|
||||
IconReact
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
let showDropdown = false;
|
||||
</script>
|
||||
|
||||
{#if $canWritePlatforms}
|
||||
<DropList bind:show={showDropdown} placement="bottom-start">
|
||||
<Button on:click={() => (showDropdown = !showDropdown)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add platform
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => addPlatform(Platform.Flutter)}>Flutter app</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Android)}>Android app</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Apple)}>Apple app</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.ReactNative)}
|
||||
>React Native app</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
{#if $canWritePlatforms}
|
||||
<Button on:click={toggle}>
|
||||
<Icon icon={IconPlus} slot="start" />
|
||||
<span class="text">Add platform</span>
|
||||
</Button>
|
||||
{/if}
|
||||
<div style:min-width="232px" slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Web)}
|
||||
leadingIcon={IconCode}>
|
||||
Web
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Flutter)}
|
||||
leadingIcon={IconFlutter}>
|
||||
Flutter
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Android)}
|
||||
leadingIcon={IconAndroid}>
|
||||
Android
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Apple)}
|
||||
leadingIcon={IconApple}>
|
||||
Apple
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.ReactNative)}
|
||||
leadingIcon={IconReact}>
|
||||
React Native
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import { SvgIcon } from '$lib/components';
|
||||
</script>
|
||||
|
||||
<SvgIcon name="angular" type="color" iconSize="" />
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import { SvgIcon } from '$lib/components';
|
||||
</script>
|
||||
|
||||
<SvgIcon name="nextjs" type="color" iconSize="" />
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import noFrameworkIcon from './noFrameworkIcon.svg';
|
||||
import noFrameworkIconDark from './noFrameworkIconDark.svg';
|
||||
import { app } from '$lib/stores/app';
|
||||
</script>
|
||||
|
||||
{#if $app.themeInUse === 'light'}
|
||||
<img src={noFrameworkIcon} alt="" />
|
||||
{:else}
|
||||
<img src={noFrameworkIconDark} alt="" />
|
||||
{/if}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import { SvgIcon } from '$lib/components';
|
||||
</script>
|
||||
|
||||
<SvgIcon name="nuxt" type="color" iconSize="" />
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import { SvgIcon } from '$lib/components';
|
||||
</script>
|
||||
|
||||
<SvgIcon name="react" type="color" iconSize="" />
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import { SvgIcon } from '$lib/components';
|
||||
</script>
|
||||
|
||||
<SvgIcon name="svelte" type="color" iconSize="" />
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import { SvgIcon } from '$lib/components';
|
||||
</script>
|
||||
|
||||
<SvgIcon name="vue" type="color" iconSize="" />
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user