Merge branch 'feat-pink-v2' of https://github.com/appwrite/console into feat-pink-v2-legacy-fixes

This commit is contained in:
Torsten Dittmann
2025-01-28 13:23:23 +01:00
37 changed files with 1442 additions and 726 deletions
+1 -1
View File
@@ -23,7 +23,7 @@
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "1.0.0-next.7",
"@appwrite.io/pink-legacy": "^1.0.0",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@3114ccc",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@4a73ad4",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
+33 -10
View File
@@ -21,8 +21,8 @@ importers:
specifier: ^1.0.0
version: 1.0.0
'@appwrite.io/pink-svelte':
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@3114ccc
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@3114ccc(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@4a73ad4
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@4a73ad4(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -223,8 +223,8 @@ packages:
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@3114ccc0b95c9d94385c06730b7aeaef7b4a81fc':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@3114ccc0b95c9d94385c06730b7aeaef7b4a81fc}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@4a73ad48c64ce1078565d893a9c91d887d05d90a':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@4a73ad48c64ce1078565d893a9c91d887d05d90a}
version: 1.0.0-next.7
peerDependencies:
svelte: ^4.0.0
@@ -238,8 +238,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.0':
resolution: {integrity: sha512-EZk/wX8oDhIsCXf2A0oQjdNOaHvh5MnWbLQI4pAiGrdy5qQGPoU19AK6iVX6z3QoOq7geoEu0zVwAowBy5hZbQ==}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@3114ccc':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@3114ccc}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@4a73ad4':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@4a73ad4}
version: 1.0.0-next.85
peerDependencies:
react-dom: ^18.0.0
@@ -694,6 +694,11 @@ packages:
peerDependencies:
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.118
'@melt-ui/svelte@0.86.2':
resolution: {integrity: sha512-wRVN603oIt1aXvx2QRmKqVDJgTScSvr/WJLLokkD8c4QzHgn6pfpPtUKmhV6Dvkk+OY89OG/1Irkd6ouA50Ztw==}
peerDependencies:
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.118
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -4087,7 +4092,7 @@ snapshots:
dependencies:
svelte: 4.2.19
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@3114ccc0b95c9d94385c06730b7aeaef7b4a81fc(svelte@4.2.19)':
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@4a73ad48c64ce1078565d893a9c91d887d05d90a(svelte@4.2.19)':
dependencies:
svelte: 4.2.19
@@ -4100,11 +4105,12 @@ 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@3114ccc(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@4a73ad4(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@3114ccc0b95c9d94385c06730b7aeaef7b4a81fc(svelte@4.2.19)
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@4a73ad48c64ce1078565d893a9c91d887d05d90a(svelte@4.2.19)
'@floating-ui/dom': 1.6.13
'@melt-ui/svelte': 0.83.0(svelte@4.2.19)
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.2(svelte@4.2.19))(svelte@4.2.19)
'@melt-ui/svelte': 0.86.2(svelte@4.2.19)
pretty-bytes: 6.1.1
react-dom: 18.3.1(react@18.3.1)
shiki: 1.27.2
@@ -4618,6 +4624,13 @@ snapshots:
magic-string: 0.30.17
svelte: 4.2.19
'@melt-ui/pp@0.3.2(@melt-ui/svelte@0.86.2(svelte@4.2.19))(svelte@4.2.19)':
dependencies:
'@melt-ui/svelte': 0.86.2(svelte@4.2.19)
estree-walker: 3.0.3
magic-string: 0.30.17
svelte: 4.2.19
'@melt-ui/svelte@0.83.0(svelte@4.2.19)':
dependencies:
'@floating-ui/core': 1.6.9
@@ -4628,6 +4641,16 @@ snapshots:
nanoid: 5.0.9
svelte: 4.2.19
'@melt-ui/svelte@0.86.2(svelte@4.2.19)':
dependencies:
'@floating-ui/core': 1.6.9
'@floating-ui/dom': 1.6.13
'@internationalized/date': 3.6.0
dequal: 2.0.3
focus-trap: 7.6.4
nanoid: 5.0.9
svelte: 4.2.19
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
+4
View File
@@ -18,6 +18,8 @@
//temporarily unefined
export let title: Props['title'] = undefined;
export let disabled = false;
export let src: string = null;
export let alt: string = null;
// TODO: remove after label card migration
let slotTitle: HTMLSpanElement;
@@ -26,6 +28,8 @@
<Tooltip disabled={!tooltipText || !tooltipShow} inline={false}>
<div>
<Card.Selector
{src}
{alt}
{padding}
{imageRadius}
{variant}
@@ -1,5 +1,6 @@
<script lang="ts">
import { Input } from '@appwrite.io/pink-svelte';
import type { ComponentType } from 'svelte';
export let id: string;
export let label: string | undefined = undefined;
@@ -11,6 +12,7 @@
value: string | boolean | number | null;
label: string;
disabled?: boolean;
leadingIcon?: ComponentType;
}[];
let element: HTMLSelectElement;
+186
View File
@@ -0,0 +1,186 @@
import ignore from 'ignore';
export interface FileData {
path: string;
buffer: ArrayBuffer;
}
export async function processFileList(files: FileList): Promise<FileData[]> {
const fileArray = Array.from(files);
const ignorer = ignore();
// Check for .gitignore file
const gitignoreFile = fileArray.find(
(file) => file.webkitRelativePath.endsWith('.gitignore') || file.name === '.gitignore'
);
if (gitignoreFile) {
const gitignoreContent = await gitignoreFile.text();
ignorer.add(gitignoreContent);
} else {
ignorer.add(defaultIgnore);
}
// Filter files using ignorer
const filteredFiles = fileArray.filter((file) => {
const path = file.webkitRelativePath || file.name;
return !ignorer.ignores(path);
});
const filePromises = filteredFiles.map(async (file) => {
try {
const buffer = await file.arrayBuffer();
return {
path: file?.webkitRelativePath || file.name,
buffer: buffer
};
} catch (e) {
// console.log(file);
return null;
}
});
return Promise.all(filePromises);
}
export const defaultIgnore = `
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
`;
@@ -1,10 +1,10 @@
<script lang="ts">
import { Trim } from '$lib/components';
import { Link, Pill } from '$lib/elements';
import { Link } from '$lib/elements';
import { protocol } from '$routes/(console)/store';
import type { Models } from '@appwrite.io/console';
import { IconExternalLink } from '@appwrite.io/pink-icons-svelte';
import { ActionMenu, Icon, Layout, Popover } from '@appwrite.io/pink-svelte';
import { ActionMenu, Icon, Layout, Popover, Tag } from '@appwrite.io/pink-svelte';
export let domains: Models.ProxyRuleList;
</script>
@@ -20,9 +20,9 @@
</Link>
{#if domains.rules.length > 1}
<Popover let:toggle>
<Pill button on:click={toggle}>
<Tag size="s" on:click={toggle}>
+{domains.rules.length - 1}
</Pill>
</Tag>
<svelte:fragment slot="tooltip">
<ActionMenu.Root>
{#each domains.rules as rule, i}
@@ -4,7 +4,7 @@
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { calculateTime } from '$lib/helpers/timeConversion';
import type { Models } from '@appwrite.io/console';
import { Icon, Image, Layout, Status, Typography } from '@appwrite.io/pink-svelte';
import { Divider, Icon, Image, Layout, Status, Typography } from '@appwrite.io/pink-svelte';
import DeploymentSource from './deploymentSource.svelte';
import DeploymentCreatedBy from './deploymentCreatedBy.svelte';
import { Button } from '$lib/elements/forms';
@@ -24,92 +24,106 @@
</script>
<Card padding="xs">
<Layout.Stack gap="m" direction="row">
<Image
width={445}
height={280}
src={`https://placehold.co/600x400/111/bbb?text=Screenshot+coming+soon&font=inter`}
alt="Screenshot" />
<Layout.Stack>
<Layout.Stack direction="row" alignItems="flex-start">
<Layout.Stack direction="row" gap="xl">
{#if deployment.status === 'failed'}
<Layout.Stack gap="xs" inline>
<Typography.Text
variant="m-400"
color="--color-fgcolor-neutral-tertiary">
Status
</Typography.Text>
<Typography.Text variant="m-400">
<Status status={deployment.status} label={deployment.status} />
</Typography.Text>
</Layout.Stack>
{:else}
<Layout.Stack gap="xs" inline>
<Typography.Text
variant="m-400"
color="--color-fgcolor-neutral-tertiary">
Deployed
</Typography.Text>
<DeploymentCreatedBy {deployment} />
</Layout.Stack>
<Layout.Stack>
<Layout.Stack gap="m" direction="row">
<Image
width={445}
height={280}
src={`https://placehold.co/600x400/111/bbb?text=Screenshot+coming+soon&font=inter`}
alt="Screenshot" />
<Layout.Stack>
<Layout.Stack direction="row" alignItems="flex-start">
<Layout.Stack direction="row" gap="xl">
{#if deployment.status === 'failed'}
<Layout.Stack gap="xs" inline>
<Typography.Text
variant="m-400"
color="--color-fgcolor-neutral-tertiary">
Status
</Typography.Text>
<Typography.Text variant="m-400">
<Status status={deployment.status} label={deployment.status} />
</Typography.Text>
</Layout.Stack>
{:else}
<Layout.Stack gap="xs" inline>
<Typography.Text
variant="m-400"
color="--color-fgcolor-neutral-tertiary">
Deployed
</Typography.Text>
<DeploymentCreatedBy {deployment} />
</Layout.Stack>
{/if}
</Layout.Stack>
{#if siteUrl}
<Button icon text on:click={() => (show = true)}>
<Icon icon={IconQrcode} />
</Button>
{/if}
</Layout.Stack>
{#if siteUrl}
<Button icon text on:click={() => (show = true)}>
<Icon icon={IconQrcode} />
</Button>
{/if}
</Layout.Stack>
{#if proxyRuleList?.total}
<Layout.Stack gap="xs">
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-tertiary">
Domains
</Typography.Text>
<DeploymentDomains domains={proxyRuleList} />
</Layout.Stack>
{:else if deployment.domain}
<Layout.Stack gap="xs">
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-tertiary">
Domains
</Typography.Text>
<Link external href={`${$protocol}${deployment.domain}`} variant="muted">
<Layout.Stack gap="xxs" direction="row" alignItems="center">
<Trim alternativeTrim>
{deployment.domain}
</Trim>
<Icon icon={IconExternalLink} size="s" />
</Layout.Stack>
</Link>
</Layout.Stack>
{/if}
<Layout.Stack gap="xl" direction="row">
{#if deployment?.buildTime}
<Layout.Stack gap="xs" inline>
{#if proxyRuleList?.total}
<Layout.Stack gap="xs">
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-tertiary">
Build time
Domains
</Typography.Text>
<Typography.Text variant="m-400">
{calculateTime(deployment.buildTime)}
<DeploymentDomains domains={proxyRuleList} />
</Layout.Stack>
{:else if deployment.domain}
<Layout.Stack gap="xs">
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-tertiary">
Domains
</Typography.Text>
<Link external href={`${$protocol}${deployment.domain}`} variant="muted">
<Layout.Stack gap="xxs" direction="row" alignItems="center">
<Trim alternativeTrim>
{deployment.domain}
</Trim>
<Icon icon={IconExternalLink} size="s" />
</Layout.Stack>
</Link>
</Layout.Stack>
{/if}
<Layout.Stack gap="xs" inline>
<Layout.Stack gap="xl" direction="row">
{#if deployment?.buildTime}
<Layout.Stack gap="xs" inline>
<Typography.Text
variant="m-400"
color="--color-fgcolor-neutral-tertiary">
Build time
</Typography.Text>
<Typography.Text variant="m-400">
{calculateTime(deployment.buildTime)}
</Typography.Text>
</Layout.Stack>
{/if}
<Layout.Stack gap="xs" inline>
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-tertiary">
Total size
</Typography.Text>
<Typography.Text variant="m-400">
{totalSize.value}{totalSize.unit}
</Typography.Text>
</Layout.Stack>
</Layout.Stack>
<Layout.Stack gap="xs">
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-tertiary">
Total size
</Typography.Text>
<Typography.Text variant="m-400">
{totalSize.value}{totalSize.unit}
Source
</Typography.Text>
<DeploymentSource {deployment} />
</Layout.Stack>
</Layout.Stack>
<Layout.Stack gap="xs">
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-tertiary">
Source
</Typography.Text>
<DeploymentSource {deployment} />
</Layout.Stack>
</Layout.Stack>
{#if $$slots.footer}
<span
style="margin-left: calc(-1* var(--space-6));margin-right: calc(-1* var(--space-6));width:auto;">
<Divider />
</span>
<Layout.Stack direction="row-reverse">
<slot name="footer" />
</Layout.Stack>
{/if}
</Layout.Stack>
</Card>
@@ -1,16 +1,10 @@
<script lang="ts">
import { Card } from '$lib/components';
import { Icon, Layout, Typography } from '@appwrite.io/pink-svelte';
import {
IconGithub,
IconReact,
IconSvelte,
IconNuxt,
IconVue,
IconGitBranch
} from '@appwrite.io/pink-icons-svelte';
import { IconGithub, IconGitBranch } from '@appwrite.io/pink-icons-svelte';
import { consoleVariables } from '$routes/(console)/store';
import type { Models } from '@appwrite.io/console';
import { getFrameworkIcon } from './store';
export let framework: Partial<Models.Framework>;
export let repositoryName: string;
@@ -18,21 +12,6 @@
export let rootDir: string;
export let domain: string = '';
export let showAfter = true;
function getIcon(fr: string) {
switch (true) {
case fr.toLocaleLowerCase().includes('sveltekit'):
return IconSvelte;
case fr.toLocaleLowerCase().includes('nuxt'):
return IconNuxt;
case fr.toLocaleLowerCase().includes('vue'):
return IconVue;
case fr.toLocaleLowerCase().includes('react'):
return IconReact;
default:
return undefined;
}
}
</script>
<Card padding="xs">
@@ -40,7 +19,7 @@
<slot />
<Layout.Stack gap="l">
{#if framework?.name}
{@const frameworkIcon = getIcon(framework.key)}
{@const frameworkIcon = getFrameworkIcon(framework.key)}
<Layout.Stack gap="xxxs">
<Typography.Caption variant="400">Framework</Typography.Caption>
<Layout.Stack gap="xs" alignItems="center" direction="row">
@@ -1,9 +1,9 @@
<script lang="ts">
import { CustomId } from '$lib/components';
import { Pill } from '$lib/elements';
import { InputSelect, InputText } from '$lib/elements/forms';
import type { Models } from '@appwrite.io/console';
import { Fieldset, Layout } from '@appwrite.io/pink-svelte';
import { Fieldset, Layout, Tag } from '@appwrite.io/pink-svelte';
import type { ComponentType } from 'svelte';
export let name: string;
export let id: string;
@@ -12,6 +12,7 @@
export let options: {
value: string;
label: string;
leadingIcon?: ComponentType;
}[] = [];
let showCustomId = false;
@@ -31,10 +32,10 @@
<CustomId bind:id bind:show={showCustomId} name="Site" fullWidth />
{:else}
<div>
<Pill button on:click={() => (showCustomId = !showCustomId)}>
<Tag size="s" on:click={() => (showCustomId = !showCustomId)}>
<span class="icon-pencil" aria-hidden="true" />
<span class="text">Site ID </span>
</Pill>
</Tag>
</div>
{/if}
</Layout.Stack>
@@ -0,0 +1,181 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Button, Form } from '$lib/elements/forms';
import { Wizard } from '$lib/layout';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Layout, Icon, Upload, Typography, Tooltip } from '@appwrite.io/pink-svelte';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { writable } from 'svelte/store';
import Details from '../details.svelte';
import Configuration from './configuration.svelte';
import Aside from './aside.svelte';
import { BuildRuntime, Framework, ID, Query } from '@appwrite.io/console';
import type { Models } from '@appwrite.io/console';
import { processFileList } from '$lib/helpers/files';
import { createTarGzip } from 'nanotar';
export let data;
let showExitModal = false;
let formComponent: Form;
let isSubmitting = writable(false);
let name = '';
let id = '';
let framework: Models.Framework = data.frameworks.frameworks[0];
let adapter = framework?.adapters[0];
let installCommand = adapter?.installCommand;
let buildCommand = adapter?.buildCommand;
let outputDirectory = adapter?.outputDirectory;
let variables: Partial<Models.Variable>[] = [];
let files: FileList;
let uploadFile: File;
async function handleChange() {
const tick = performance.now();
if (!files?.length) return;
// If the file is a tar.gz file, then return it as is
if (
files?.length === 1 &&
files.item(0).type === 'application/gzip' &&
files.item(0).name.split('.').pop() === 'tar'
) {
uploadFile = files.item(0);
}
// Else process the file to mantain the folder structure and create a .tar.gz file
else {
try {
const processedFiles = await processFileList(files);
const tar = await createTarGzip(
processedFiles.map((f) => ({
name: f.path,
data: f.buffer
}))
);
const blob = new Blob([tar], { type: 'application/gzip' });
const file = new File([blob], 'deployment.tar.gz', { type: 'application/gzip' });
uploadFile = file;
} catch (e) {
addNotification({
type: 'error',
message: e
});
}
}
console.log(uploadFile);
const tock = performance.now();
console.log('Time taken to process files:', tock - tick);
}
async function create() {
try {
const fr = Object.values(Framework).find((f) => f === framework.key);
const buildRuntime = Object.values(BuildRuntime).find(
(f) => f === framework.buildRuntime
);
let site = await sdk.forProject.sites.create(
id || ID.unique(),
name,
fr,
buildRuntime,
undefined,
undefined,
installCommand,
buildCommand,
outputDirectory,
undefined,
framework.adapters[Object.keys(framework.adapters)[0]].key, //TODO: fix this
undefined,
undefined,
undefined,
undefined,
undefined,
undefined
);
trackEvent(Submit.SiteCreate, {});
const { deployments } = await sdk.forProject.sites.listDeployments(site.$id, [
Query.limit(1)
]);
console.log(deployments);
const deployment = deployments[0];
await goto(
`${base}/project-${$page.params.project}/sites/create-site/deploying?site=${site.$id}&deployment=${deployment.$id}`
);
} catch (e) {
addNotification({
type: 'error',
message: e.message
});
trackError(e, Submit.SiteCreate);
}
}
</script>
<svelte:head>
<title>Create site - Appwrite</title>
</svelte:head>
<Wizard
title="Create site"
bind:showExitModal
href={`${base}/project-${$page.params.project}/sites/`}
confirmExit>
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
<Layout.Stack gap="xl">
<Upload.Dropzone folder bind:files on:change={handleChange}>
<Layout.Stack alignItems="center" gap="s">
<Layout.Stack
alignItems="center"
justifyContent="center"
direction="row"
gap="s">
<Typography.Text variant="l-500">
Drag and drop files here or click to upload
</Typography.Text>
<Tooltip>
<Icon icon={IconInfo} size="s" />
<svelte:fragment slot="tooltip">
Only PNG, JPEG, PDF files allowed
</svelte:fragment>
</Tooltip>
</Layout.Stack>
<Typography.Caption variant="400">Max file size: 10MB</Typography.Caption>
</Layout.Stack>
</Upload.Dropzone>
<Details bind:name bind:id />
<Configuration
bind:installCommand
bind:buildCommand
bind:outputDirectory
bind:selectedFramework={framework}
bind:variables
frameworks={data.frameworks.frameworks} />
<!-- <Domain /> -->
</Layout.Stack>
</Form>
<svelte:fragment slot="aside">
<Aside />
</svelte:fragment>
<svelte:fragment slot="footer">
<Button size="s" fullWidthMobile secondary on:click={() => (showExitModal = true)}>
Cancel
</Button>
<Button
size="s"
fullWidthMobile
on:click={() => formComponent.triggerSubmit()}
disabled={$isSubmitting}>
Deploy
</Button>
</svelte:fragment>
</Wizard>
@@ -0,0 +1,7 @@
export const load = async ({ parent }) => {
const { frameworks } = await parent();
return {
frameworks
};
};
@@ -0,0 +1,7 @@
<script lang="ts">
import { Card } from '$lib/components';
</script>
<Card padding="xs">
<slot />
</Card>
@@ -0,0 +1,225 @@
<script lang="ts">
import { Empty } from '$lib/components';
import { Button, InputSelect, InputText } from '$lib/elements/forms';
import {
Fieldset,
Layout,
Popover,
Icon,
Table,
Badge,
HiddenText,
ActionMenu,
Accordion
} from '@appwrite.io/pink-svelte';
import {
IconDotsHorizontal,
IconCode,
IconUpload,
IconPlus,
IconTrash,
IconEyeOff,
IconPencil
} from '@appwrite.io/pink-icons-svelte';
import SecretVariableModal from '../secretVariableModal.svelte';
import ImportSiteVariablesModal from '../importSiteVariablesModal.svelte';
import type { Models } from '@appwrite.io/console';
import { getFrameworkIcon } from '../store';
export let frameworks: Models.Framework[];
export let selectedFramework: Models.Framework;
export let variables: Partial<Models.Variable>[] = [];
export let installCommand = '';
export let buildCommand = '';
export let outputDirectory = '';
let showEditorModal = false;
let showImportModal = false;
let showSecretModal = false;
let currentVariable: Partial<Models.Variable>;
let frameworkId = selectedFramework.key;
function markAsSecret() {
let variable = variables.find((v) => v.key === currentVariable.key);
if (variable) {
variable.secret = true;
}
}
$: frameworkData = frameworks.find((framework) => framework.key === selectedFramework.key);
</script>
<Fieldset legend="Configuration">
<Layout.Stack gap="l">
<InputSelect
id="framework"
label="Framework"
placeholder="Select framework"
bind:value={frameworkId}
options={frameworks.map((framework) => ({
value: framework.key,
label: framework.name
}))}
on:change={() => {
selectedFramework = frameworks.find((framework) => framework.key === frameworkId);
}} />
<Layout.Stack>
<Accordion title="Build settings" badge="Optional">
<Layout.Stack>
Set up how your project is built and where the output files are stored.
<Layout.Stack gap="s" direction="row" alignItems="flex-end">
<InputText
id="installCommand"
label="Install command"
bind:value={installCommand}
placeholder={frameworkData?.defaultInstallCommand} />
<Button secondary size="s" on:click={() => (installCommand = '')}>
Reset
</Button>
</Layout.Stack>
<Layout.Stack gap="s" direction="row" alignItems="flex-end">
<InputText
id="buildCommand"
label="Build command"
bind:value={buildCommand}
placeholder={frameworkData?.defaultBuildCommand} />
<Button secondary size="s" on:click={() => (buildCommand = '')}>
Reset
</Button>
</Layout.Stack>
<Layout.Stack gap="s" direction="row" alignItems="flex-end">
<InputText
id="outputDirectory"
label="Output directory"
bind:value={outputDirectory}
placeholder={frameworkData?.defaultOutputDirectory} />
<Button secondary size="s" on:click={() => (outputDirectory = '')}>
Reset
</Button>
</Layout.Stack>
</Layout.Stack>
</Accordion>
<Accordion title="Environment variables" badge="Optional" hideDivider>
<Layout.Stack gap="l">
<Layout.Stack gap="xl">
Set up environment variables to securely manage keys and settings for your
project.
{#if variables?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell width="200px">Key</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Header.Cell width="10px"></Table.Header.Cell>
</svelte:fragment>
{#each variables as variable}
<Table.Row>
<Table.Cell>{variable.key}</Table.Cell>
<Table.Cell>
<!-- TODO: fix max width -->
<div style="max-width: 20rem">
{#if variable.secret}
<Badge content="Secret" variant="secondary" />
{:else}
<HiddenText
isVisible={false}
text={variable.value} />
{/if}
</div>
</Table.Cell>
<Table.Cell>
<div style="margin-inline-start: auto">
<Popover placement="bottom-end" let:toggle>
<Button
text
icon
on:click={(e) => {
e.preventDefault();
toggle(e);
}}>
<Icon size="s" icon={IconDotsHorizontal} />
</Button>
<svelte:fragment slot="tooltip">
<ActionMenu.Root>
<ActionMenu.Item.Button
trailingIcon={IconPencil}
on:click={() => {
showEditorModal = true;
}}>
Edit
</ActionMenu.Item.Button>
{#if !variable?.secret}
<ActionMenu.Item.Button
trailingIcon={IconEyeOff}
on:click={() => {
currentVariable = variable;
showSecretModal = true;
}}>
Secret
</ActionMenu.Item.Button>
{/if}
<ActionMenu.Item.Button
trailingIcon={IconTrash}
on:click={() => {
showImportModal = true;
}}>
Delete
</ActionMenu.Item.Button>
</ActionMenu.Root>
</svelte:fragment>
</Popover>
</div>
</Table.Cell>
</Table.Row>
{/each}
</Table.Root>
{:else}
<Empty>Create variables to get started</Empty>
{/if}
</Layout.Stack>
<Layout.Stack direction="row">
<Layout.Stack direction="row">
<Button
secondary
size="s"
on:mousedown={() => (showEditorModal = true)}>
<Icon icon={IconCode} /> Editor
</Button>
<Button
secondary
size="s"
on:mousedown={() => (showImportModal = true)}>
<Icon icon={IconUpload} /> Import .env
</Button>
</Layout.Stack>
<Button secondary size="s" on:mousedown={() => (showImportModal = true)}>
<Icon icon={IconPlus} /> Create variable
</Button>
</Layout.Stack>
</Layout.Stack>
</Accordion>
</Layout.Stack>
</Layout.Stack>
</Fieldset>
{#if showEditorModal}
<!-- <RawVariableEditor
{isGlobal}
{sdkCreateVariable}
{sdkUpdateVariable}
{sdkDeleteVariable}
{variableList}
bind:showEditor={showEditorModal} /> -->
{/if}
{#if showSecretModal}
<SecretVariableModal bind:show={showSecretModal} on:click={markAsSecret} />
{/if}
{#if showImportModal}
<ImportSiteVariablesModal bind:show={showImportModal} bind:variables />
{/if}
@@ -1,16 +1,16 @@
<script lang="ts">
import { Collapsible, CollapsibleItem, Empty } from '$lib/components';
import { Empty } from '$lib/components';
import { Button, InputSelect, InputText } from '$lib/elements/forms';
import {
Fieldset,
Layout,
Tag,
Popover,
Icon,
Table,
Badge,
HiddenText,
ActionMenu
ActionMenu,
Accordion
} from '@appwrite.io/pink-svelte';
import {
IconDotsHorizontal,
@@ -21,9 +21,10 @@
IconEyeOff,
IconPencil
} from '@appwrite.io/pink-icons-svelte';
import SecretVariableModal from './secretVariableModal.svelte';
import ImportSiteVariablesModal from './importSiteVariablesModal.svelte';
import SecretVariableModal from '../../secretVariableModal.svelte';
import ImportSiteVariablesModal from '../../importSiteVariablesModal.svelte';
import type { Models } from '@appwrite.io/console';
import { getFrameworkIcon } from '../../store';
export let frameworks: Models.Framework[];
export let selectedFramework: Models.Framework;
@@ -59,17 +60,15 @@
bind:value={frameworkId}
options={frameworks.map((framework) => ({
value: framework.key,
label: framework.name
label: framework.name,
leadingIcon: getFrameworkIcon(framework.key)
}))}
on:change={() => {
selectedFramework = frameworks.find((framework) => framework.key === frameworkId);
}} />
<Collapsible>
<CollapsibleItem>
<svelte:fragment slot="title">
Build settings <Tag size="s">Optional</Tag>
</svelte:fragment>
<Layout.Stack>
<Accordion title="Build settings" badge="Optional">
<Layout.Stack>
Set up how your project is built and where the output files are stored.
<Layout.Stack gap="s" direction="row" alignItems="flex-end">
@@ -103,11 +102,11 @@
</Button>
</Layout.Stack>
</Layout.Stack>
</CollapsibleItem>
</Accordion>
<CollapsibleItem>
<Accordion title="Environment variables" badge="Optional" hideDivider>
<svelte:fragment slot="title">
Environment variables <Tag size="s">Optional</Tag>
Environment variables <Badge content="Optional" variant="secondary" />
</svelte:fragment>
<Layout.Stack gap="l">
<Layout.Stack gap="xl">
@@ -206,8 +205,8 @@
</Button>
</Layout.Stack>
</Layout.Stack>
</CollapsibleItem>
</Collapsible>
</Accordion>
</Layout.Stack>
</Layout.Stack>
</Fieldset>
@@ -6,7 +6,7 @@
export let show = false;
</script>
<Modal title="Secret variable" size="big" bind:show>
<Modal title="Secret variable" bind:show>
<Layout.Stack gap="l">
<p>
Secret variables are hidden from both the UI and API. Once a variable is marked as
@@ -0,0 +1,30 @@
import {
IconAngular,
IconNuxt,
IconReact,
IconSvelte,
IconVue
} from '@appwrite.io/pink-icons-svelte';
export function getFrameworkIcon(framework: string) {
console.log(framework);
switch (true) {
case framework.toLocaleLowerCase().includes('sveltekit'):
return IconSvelte;
case framework.toLocaleLowerCase().includes('nuxt'):
return IconNuxt;
case framework.toLocaleLowerCase().includes('vue'):
return IconVue;
case framework.toLocaleLowerCase().includes('react'):
return IconReact;
case framework.toLocaleLowerCase().includes('angular'):
return IconAngular;
case framework.toLocaleLowerCase().includes('svelte'):
return IconSvelte;
case framework.toLocaleLowerCase().includes('next'):
return IconReact; //TODO: add next icon
default:
return undefined;
}
}
@@ -1,21 +1,25 @@
<script lang="ts">
import { base } from '$app/paths';
import { Collapsible, CollapsibleItem, EmptySearch, SvgIcon } from '$lib/components';
import { EmptySearch } from '$lib/components';
import { Button, InputSearch } from '$lib/elements/forms';
import { page } from '$app/stores';
import Wizard from '$lib/layout/wizard.svelte';
import { goto } from '$app/navigation';
import { debounce } from '$lib/helpers/debounce.js';
import { Card, Layout } from '@appwrite.io/pink-svelte';
import { templatesList } from '$lib/stores/templates.js';
import { Card, Layout, Accordion, Selector } from '@appwrite.io/pink-svelte';
export let data;
function applyFilter(filter: string, value: string, event: Event) {
const add = (event.target as EventTarget & HTMLInputElement).checked;
function applyFilter(filter: string, value: string, event: CustomEvent) {
const target = new URL($page.url);
if (add) {
target.searchParams.append(filter, value);
if (event?.detail) {
if (
!target.searchParams
.getAll(filter)
.some((n) => n.toLowerCase() === value.toLowerCase())
) {
target.searchParams.append(filter, value);
}
} else {
const previous = target.searchParams
.getAll(filter)
@@ -42,27 +46,6 @@
}, 250)();
}
function getIconFromFramework(framework: string) {
switch (true) {
case framework.includes('node'):
return 'node';
case framework.includes('php'):
return 'php';
case framework.includes('ruby'):
return 'ruby';
case framework.includes('python'):
return 'python';
case framework.includes('dart'):
return 'dart';
case framework.includes('bun'):
return 'bun';
case framework.includes('go'):
return 'go';
default:
return undefined;
}
}
function clearSearch() {
const target = new URL($page.url);
target.search = '';
@@ -74,19 +57,6 @@
.getAll('useCase')
.some((param) => param.toLowerCase() === useCase.toLowerCase());
};
$: getErrorMessage = () => {
const searchParams = $page.url.searchParams;
const paramsArray = Array.from(searchParams.entries());
if (paramsArray.length === 1) {
const [_, value] = paramsArray[0];
return `Sorry, we couldn't find "${value}".`;
} else if (paramsArray.length > 1) {
return `Sorry, we couldn't find any results with the applied filters.`;
}
};
$: console.log(templatesList);
</script>
<svelte:head>
@@ -101,52 +71,41 @@
value={$page.url.searchParams.get('search')}
on:clear={clearSearch}
on:change={applySearch} />
<Collapsible>
<CollapsibleItem>
<svelte:fragment slot="title">Use case</svelte:fragment>
<ul class="form-list u-row-gap-16">
<Layout.Stack>
<Accordion title="Use case">
<Layout.Stack>
{#each [...data.useCases] as useCase}
<li class="form-item">
<label class="u-flex u-cross-center u-gap-16">
<input
type="checkbox"
class="is-small"
checked={isChecked(useCase)}
on:change={(e) => applyFilter('useCase', useCase, e)} />
<span class="u-trim-1">{useCase}</span>
</label>
</li>
<Layout.Stack direction="row" gap="s">
<Selector.Checkbox
id={useCase}
size="s"
label={useCase}
checked={isChecked(useCase)}
on:change={(e) => {
applyFilter('useCase', useCase, e);
}} />
</Layout.Stack>
{/each}
</ul>
</CollapsibleItem>
<CollapsibleItem>
<svelte:fragment slot="title">Framework</svelte:fragment>
<ul class="form-list u-row-gap-16">
</Layout.Stack>
</Accordion>
<Accordion title="Framework">
<Layout.Stack>
{#each [...data.frameworks] as framework}
{@const icon = getIconFromFramework(framework)}
<li class="form-item">
<label class="u-flex u-cross-center u-gap-16">
<input
type="checkbox"
class="is-small"
checked={$page.url.searchParams
.getAll('framework')
.includes(framework)}
on:change={(e) => applyFilter('framework', framework, e)} />
<div class="u-flex u-cross-center u-gap-8">
<div class="avatar is-size-x-small">
<SvgIcon name={icon} iconSize="small" />
</div>
<div class="u-trim-1 u-capitalize">
{framework?.split('-')?.join(' ')}
</div>
</div>
</label>
</li>
<Layout.Stack direction="row" gap="s">
<Selector.Checkbox
id={framework}
size="s"
label={framework?.split('-')?.join(' ')}
checked={$page.url.searchParams
.getAll('framework')
.includes(framework)}
on:change={(e) => applyFilter('framework', framework, e)} />
</Layout.Stack>
{/each}
</ul>
</CollapsibleItem>
</Collapsible>
</Layout.Stack>
</Accordion>
</Layout.Stack>
</Layout.Stack>
</svelte:fragment>
@@ -170,16 +129,11 @@
{/each}
</ul>
{:else}
<EmptySearch hidePagination>
<div class="common-section">
<div class="u-text-center common-section">
<b class="body-text-2 u-bold">{getErrorMessage()}</b>
<p>There are no templates that match your search.</p>
</div>
<div class="u-flex u-gap-16 common-section u-main-center">
<Button secondary on:click={clearSearch}>Clear search</Button>
</div>
</div>
<EmptySearch
hidePagination
target="templates"
search={$page.url.searchParams.get('search')}>
<Button secondary on:click={clearSearch}>Clear search</Button>
</EmptySearch>
{/if}
<!-- <div class="u-flex u-margin-block-start-32 u-main-space-between u-cross-center">
@@ -20,19 +20,19 @@ export const load = async ({ url, route }) => {
const [frameworks, useCases] = siteTemplatesList.templates.reduce(
([fr, uc], next) => {
next.useCases.forEach((useCase) => uc.add(useCase));
// next.frameworks.forEach((framework) => fr.add(framework.name));
next.frameworks.forEach((framework) => fr.add(framework.name));
return [fr, uc];
},
[new Set<string>(), new Set<string>()]
);
const templates = siteTemplatesList.templates.filter((template) => {
// if (
// filter.framework.length > 0
// && !template.frameworks.some((n) => filter.frameworks.includes(n.name))
// ) {
// return false;
// }
if (
filter.frameworks.length > 0 &&
!template.frameworks.some((n) => filter.frameworks.includes(n.name))
) {
return false;
}
const filterLowerCases = filter.useCases.map((n) => n.toLowerCase());
if (
@@ -29,6 +29,7 @@
import { BuildRuntime, Framework, ID, Query } from '@appwrite.io/console';
import Domain from '../../domain.svelte';
import { NewRepository, Repositories, RepositoryBehaviour } from '$lib/components/git';
import { getFrameworkIcon } from '../../store';
export let data;
@@ -223,7 +224,8 @@
{@const options = data.template.frameworks.map((framework) => {
return {
value: framework.name,
label: framework.name
label: framework.name,
leadingIcon: getFrameworkIcon(framework.key)
};
})}
<Layout.Stack gap="xxl">
@@ -1,7 +1,7 @@
import { sdk } from '$lib/stores/sdk';
export const load = async ({ parent, params }) => {
const { installations, frameworks } = await parent();
let { installations, frameworks } = await parent();
const template = await sdk.forProject.sites.getTemplate(params.template);
return {
@@ -1,5 +1,4 @@
<script lang="ts">
import { Collapsible, CollapsibleItem } from '$lib/components';
import {
Button,
InputEmail,
@@ -10,7 +9,7 @@
InputURL
} from '$lib/elements/forms';
import type { Models } from '@appwrite.io/console';
import { Fieldset, Layout, Tag, Popover, Icon } from '@appwrite.io/pink-svelte';
import { Fieldset, Layout, Popover, Icon, Accordion } from '@appwrite.io/pink-svelte';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import type { SvelteComponent } from 'svelte';
@@ -51,10 +50,9 @@
<Fieldset legend="Configuration">
<Layout.Stack gap="l">
<Collapsible>
<Layout.Stack>
{#if requiredVariables?.length}
<CollapsibleItem open>
<svelte:fragment slot="title">Required environment variables</svelte:fragment>
<Accordion title="Required environment variables" open>
<Layout.Stack>
Provide the values for the required environment variables to run this
application.
@@ -94,13 +92,10 @@
</Layout.Stack>
{/each}
</Layout.Stack>
</CollapsibleItem>
</Accordion>
{/if}
{#if optionalVariables?.length}
<CollapsibleItem>
<svelte:fragment slot="title">
Environment variables <Tag size="s">Optional</Tag>
</svelte:fragment>
<Accordion title="Environment variables" badge="Optional">
<Layout.Stack>
Set up environment variables to securely manage keys and settings for your
project.
@@ -140,8 +135,8 @@
</Layout.Stack>
{/each}
</Layout.Stack>
</CollapsibleItem>
</Accordion>
{/if}
</Collapsible>
</Layout.Stack>
</Layout.Stack>
</Fieldset>
@@ -1,7 +1,6 @@
<script lang="ts">
import { LabelCard, Modal } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { Layout } from '@appwrite.io/pink-svelte';
import { Card, Modal } from '$lib/components';
import { Image, Layout, Typography } from '@appwrite.io/pink-svelte';
import { base } from '$app/paths';
import { page } from '$app/stores';
import RepoDark from './(images)/repositoryDark.svg';
@@ -9,57 +8,51 @@
import TemplateDark from './(images)/templateDark.svg';
import TemplateLight from './(images)/templateLight.svg';
import { app } from '$lib/stores/app';
import { Link } from '$lib/elements';
export let show = false;
let behaviour: 'repositories' | 'templates';
$: if (!show) {
behaviour = null;
}
</script>
<Modal title="Create site" bind:show>
<Modal title="Create site" bind:show hideFooter>
<Layout.Stack direction="row" gap="m" wrap="wrap">
<div style="flex-grow: 1; min-width: 245px">
<Layout.Stack direction="column" justifyContent="center">
<LabelCard
variant="secondary"
padding="xs"
radius="s"
imageRadius="xxs"
src={$app?.themeInUse === 'dark' ? RepoDark : RepoLight}
title="Connect a repository"
name="connect"
bind:group={behaviour}
value="repositories">
Connect to one of your existing Git repositories
</LabelCard>
</Layout.Stack>
<Card
variant="secondary"
padding="xs"
radius="s"
href={`${base}/project-${$page.params.project}/sites/create-site/templates`}>
<Image
alt="repositories"
radius="xxs"
src={$app?.themeInUse === 'dark' ? TemplateDark : TemplateLight} />
<Layout.Stack gap="s">
<Typography.Text variant="m-600">Clone a template</Typography.Text>
Clone a pre-built template to quickly set up your site
</Layout.Stack>
</Card>
</div>
<div style="flex-grow: 1; min-width: 245px">
<Layout.Stack direction="column" justifyContent="center">
<LabelCard
variant="secondary"
padding="xs"
radius="s"
imageRadius="xxs"
src={$app?.themeInUse === 'dark' ? TemplateDark : TemplateLight}
title="Clone a template"
name="clone"
bind:group={behaviour}
value="templates">
Clone a pre-built template to quickly set up your site
</LabelCard>
</Layout.Stack>
<Card
variant="secondary"
padding="xs"
radius="s"
href={`${base}/project-${$page.params.project}/sites/create-site/repositories`}>
<Image
alt="repositories"
radius="xxs"
src={$app?.themeInUse === 'dark' ? RepoDark : RepoLight} />
<Layout.Stack gap="s">
<Typography.Text variant="m-600">Connect a repository</Typography.Text>
Connect to one of your existing Git repositories
</Layout.Stack>
</Card>
</div>
</Layout.Stack>
<svelte:fragment slot="footer">
<Button text size="s" on:click={() => (show = false)}>Cancel</Button>
<Button
disabled={!behaviour}
size="s"
href={`${base}/project-${$page.params.project}/sites/create-site/${behaviour}`}>
Next
</Button>
</svelte:fragment>
<Typography.Text variant="m-400">
Or simply drag and drop your files to create a site <Link
href={`${base}/project-${$page.params.project}/sites/create-site/manual`}>
manually</Link
>.
</Typography.Text>
</Modal>
@@ -4,24 +4,47 @@
import SiteCard from '../(components)/siteCard.svelte';
import DomainsOverview from './domainsOverview.svelte';
import DeploymentsOverview from './deploymentsOverview.svelte';
import { Button } from '$lib/elements/forms';
import { protocol } from '$routes/(console)/store';
import InstantRollbackDomain from './instantRollbackDomain.svelte';
export let data;
let showRollback = false;
$: console.log(data.site);
$: console.log(data.deployment);
$: console.log(data.deploymentList);
$: console.log(data.proxyRuleList);
</script>
<Container>
<Layout.Stack gap="xxl">
<SiteCard deployment={data.deployment} proxyRuleList={data.proxyRuleList} />
<SiteCard deployment={data.deployment} proxyRuleList={data.proxyRuleList}>
<svelte:fragment slot="footer">
<Button external href={`${$protocol}${data.deployment.domain}`}>Visit</Button>
<!-- TODO: disable when disabled={data.hasProdReadyDeployments} -->
<Button secondary on:click={() => (showRollback = true)}>Instant rollback</Button>
</svelte:fragment>
</SiteCard>
<Divider />
<Layout.Stack direction="row" gap="xxl">
<DomainsOverview proxyRuleList={data.proxyRuleList} />
<DeploymentsOverview
activeDeployment={data.deployment}
deploymentList={data.deploymentList} />
<Layout.Stack direction="row" gap="xxl" wrap="wrap">
<div style:flex="1">
<DomainsOverview proxyRuleList={data.proxyRuleList} />
</div>
<div style:flex="2">
<DeploymentsOverview
activeDeployment={data.deployment}
deploymentList={data.deploymentList} />
</div>
</Layout.Stack>
</Layout.Stack>
</Container>
{#if showRollback}
<InstantRollbackDomain
bind:show={showRollback}
deployment={data.deployment}
proxyRuleList={data.proxyRuleList} />
{/if}
@@ -7,14 +7,20 @@ export const load = async ({ params, depends, parent }) => {
depends(Dependencies.SITES_DOMAINS);
const { site, proxyRuleList } = await parent();
const [deployment, deploymentList] = await Promise.all([
const [deployment, deploymentList, prodReadyDeployments] = await Promise.all([
sdk.forProject.sites.getDeployment(params.site, site.deploymentId),
sdk.forProject.sites.listDeployments(params.site, [Query.limit(5), Query.orderDesc('')])
sdk.forProject.sites.listDeployments(params.site, [Query.limit(5), Query.orderDesc('')]),
sdk.forProject.sites.listDeployments(params.site, [
Query.limit(1)
// Query.equal('status', 'ready')
// Query.equal('live', true)
])
]);
return {
site,
deployment,
deploymentList,
proxyRuleList
proxyRuleList,
hasProdReadyDeployments: prodReadyDeployments?.deployments?.length > 0
};
};
@@ -13,11 +13,11 @@
async function handleSubmit() {
try {
await sdk.forProject.functions.updateDeploymentBuild(
await sdk.forProject.sites.updateDeploymentBuild(
selectedDeployment.resourceId,
selectedDeployment.$id
);
await invalidate(Dependencies.FUNCTION);
await invalidate(Dependencies.SITE);
showCancel = false;
addNotification({
type: 'success',
@@ -3,18 +3,13 @@
import { Modal } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { processFileList } from '$lib/helpers/files';
import { addNotification } from '$lib/stores/notifications';
import { uploader } from '$lib/stores/uploader';
import type { Models } from '@appwrite.io/console';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { Icon, Layout, Tooltip, Typography, Upload } from '@appwrite.io/pink-svelte';
import { createTarGzip } from 'nanotar';
import { defaultIgnore } from './store';
import ignore from 'ignore';
interface FileData {
path: string;
buffer: ArrayBuffer;
}
export let show = false;
export let site: Models.Site;
@@ -57,44 +52,6 @@
console.log('Time taken to process files:', tock - tick);
}
async function processFileList(files: FileList): Promise<FileData[]> {
const fileArray = Array.from(files);
const ignorer = ignore();
// Check for .gitignore file
const gitignoreFile = fileArray.find(
(file) => file.webkitRelativePath.endsWith('.gitignore') || file.name === '.gitignore'
);
if (gitignoreFile) {
const gitignoreContent = await gitignoreFile.text();
ignorer.add(gitignoreContent);
} else {
ignorer.add(defaultIgnore);
}
// Filter files using ignorer
const filteredFiles = fileArray.filter((file) => {
const path = file.webkitRelativePath || file.name;
return !ignorer.ignores(path);
});
const filePromises = filteredFiles.map(async (file) => {
try {
const buffer = await file.arrayBuffer();
return {
path: file?.webkitRelativePath || file.name,
buffer: buffer
};
} catch (e) {
console.error(error);
console.log(file);
error = e;
}
});
return Promise.all(filePromises);
}
async function createDeployment() {
try {
uploader.uploadSiteDeployment(site.$id, uploadFile);
@@ -100,146 +100,3 @@ export const columns = writable<Column[]>([
format: 'datetime'
}
]);
export const defaultIgnore = `
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
`;
@@ -9,7 +9,7 @@
import { type Models } from '@appwrite.io/console';
import { Link } from '$lib/elements';
import { Button } from '$lib/elements/forms';
import { Card } from '$lib/components';
import { Card, Trim } from '$lib/components';
export let proxyRuleList: Models.ProxyRuleList;
@@ -33,17 +33,15 @@
</Layout.Stack>
<Card padding="s" isTile>
{#each proxyRuleList?.rules?.slice(0, 3) as rule, i}
<Layout.Stack
gap="xs"
alignItems="center"
justifyContent="space-between"
direction="row">
<Layout.Stack gap="xs">
<Layout.Stack alignItems="center" justifyContent="space-between" direction="row">
<Layout.Stack gap="xs" inline>
<Link variant="quiet-muted" href={`${$protocol}${rule.domain}`} external>
{rule.domain}
<Trim alternativeTrim>
{rule.domain}
</Trim>
<Icon icon={IconExternalLink} />
</Link>
<Typography.Text>
<Typography.Text truncate>
Added {timeFromNow(rule.$createdAt)}
</Typography.Text>
</Layout.Stack>
@@ -0,0 +1,159 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Card, LabelCard, Modal, Status } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Link } from '$lib/elements';
import { Button, InputCheckbox } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Query, type Models } from '@appwrite.io/console';
import DeploymentDomains from '../(components)/deploymentDomains.svelte';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
import { timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
import { capitalize } from '$lib/helpers/string';
import { calculateSize } from '$lib/helpers/sizeConvertion';
export let show = false;
export let deployment: Models.Deployment;
export let proxyRuleList: Models.ProxyRuleList;
export let selectedDeployment: Models.Deployment = undefined;
let selectedDeploymentId: string;
let prodDeployments: Models.DeploymentList;
let confirm = false;
onMount(async () => {
// TODO: re-enable query after backend fixes
prodDeployments = await sdk.forProject.sites.listDeployments($page.params.site, [
// Query.equal('status', 'ready')
// Query.equal('live', true)
]);
});
async function handleSubmit() {
try {
await sdk.forProject.sites.updateDeployment(
selectedDeployment.resourceId,
selectedDeployment.$id
);
await invalidate(Dependencies.SITE);
show = false;
addNotification({
type: 'success',
message: `Deployment has been deleted`
});
trackEvent(Submit.DeploymentCancel);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.DeploymentCancel);
}
}
$: if (selectedDeploymentId) {
selectedDeployment = prodDeployments.deployments.find(
(deployment) => deployment.$id === selectedDeploymentId
);
}
</script>
<Modal title="Instant rollback" bind:show onSubmit={handleSubmit}>
<svelte:fragment slot="description">
Rollback is only available for deployments that are <b>ready and previously active</b>. <Link
href="/#">Learn more</Link
>.
</svelte:fragment>
<Layout.Stack gap="xl">
<Layout.Stack gap="s">
<Layout.Stack direction="row" alignItems="center" gap="s">
<Typography.Text variant="m-400">Rollback</Typography.Text>
<DeploymentDomains domains={proxyRuleList} />
</Layout.Stack>
<Card padding="s" radius="s" isTile>
<Layout.Stack gap="s">
<Layout.Stack
direction="row"
alignItems="center"
gap="s"
justifyContent="space-between">
<Typography.Text variant="m-400"
>{toLocaleDateTime(deployment.$createdAt)}</Typography.Text>
<Badge variant="secondary" content="Current" size="s" />
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" gap="s">
<Typography.Caption variant="400" color="--color-fgcolor-neutral-secondary">
<Status status="ready">{capitalize(deployment.status)}</Status>
</Typography.Caption>
<svg
xmlns="http://www.w3.org/2000/svg"
width="2"
height="3"
viewBox="0 0 2 3"
fill="none">
<circle cx="1" cy="1.5" r="1" fill="currentColor" />
</svg>
<Typography.Caption variant="400" color="--color-fgcolor-neutral-secondary">
{calculateSize(deployment.size)}
</Typography.Caption>
<svg
xmlns="http://www.w3.org/2000/svg"
width="2"
height="3"
viewBox="0 0 2 3"
fill="none">
<circle cx="1" cy="1.5" r="1" fill="currentColor" />
</svg>
<Typography.Caption variant="400" color="--color-fgcolor-neutral-secondary">
Created {timeFromNow(deployment.$createdAt)}
{#if deployment?.providerCommitAuthorUrl && deployment?.providerCommitAuthor}
by <Link href={deployment.providerCommitAuthorUrl} external
>{deployment.providerCommitAuthor}</Link>
{/if}
</Typography.Caption>
</Layout.Stack>
</Layout.Stack>
</Card>
</Layout.Stack>
<Layout.Stack gap="s">
<Typography.Text variant="m-400">To the following deployment:</Typography.Text>
{#if prodDeployments?.deployments?.length}
{#each prodDeployments.deployments as prodDeployment}
<LabelCard
padding="s"
radius="s"
title={toLocaleDateTime(prodDeployment.$createdAt)}
bind:group={selectedDeploymentId}
value={prodDeployment.$id}>
<Layout.Stack gap="s">
<Typography.Caption
variant="400"
color="--color-fgcolor-neutral-secondary">
Created {timeFromNow(prodDeployment.$createdAt)}
{#if prodDeployment?.providerCommitAuthorUrl && prodDeployment?.providerCommitAuthor}
by <Link href={prodDeployment.providerCommitAuthorUrl} external
>{prodDeployment.providerCommitAuthor}</Link>
{/if}
</Typography.Caption>
</Layout.Stack>
</LabelCard>
{/each}
{/if}
</Layout.Stack>
{#if selectedDeployment?.$id}
<InputCheckbox
id="rollbakc"
label={`Rollback to ${toLocaleDateTime(selectedDeployment.$createdAt)}, making it the active deployment`}
bind:checked={confirm} />
{/if}
</Layout.Stack>
<svelte:fragment slot="footer">
<Button text on:click={() => (show = false)}>Cancel</Button>
<Button secondary submit disabled={!selectedDeployment?.$id || !confirm}>Rollback</Button>
</svelte:fragment>
</Modal>
+57 -3
View File
@@ -1,10 +1,64 @@
<script>
import { Container } from '$lib/layout';
import { app } from '$lib/stores/app';
import { loading } from '$routes/store';
import { Typography } from '@appwrite.io/pink-svelte';
loading.set(false);
</script>
<Container>
<slot />
</Container>
<div class="auth-bg">
<div>
<Container>
<slot />
</Container>
</div>
<footer>
<!-- TODO: change text style to eyebrow -->
<Typography.Text size="s" color="--color-fgColor-neutral-secondary">
POWERED BY
</Typography.Text>
{#if $app.themeInUse === 'dark'}
<img
src="/console/images/appwrite-logo-dark.svg"
width="120"
height="22"
alt="Appwrite Logo" />
{:else}
<img
src="/console/images/appwrite-logo-light.svg"
width="120"
height="22"
alt="Appwrite Logo" />
{/if}
</footer>
</div>
<style lang="scss">
.auth-bg {
position: fixed;
background: var(--color-bgcolor-neutral-primary, #fff);
background-size: cover;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
justify-content: space-between;
div {
flex: 1;
display: flex;
align-items: center;
}
footer {
padding: 2rem 1rem;
display: flex;
gap: 0.5rem;
justify-content: center;
align-items: center;
}
}
</style>
+19 -218
View File
@@ -1,227 +1,28 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Card } from '$lib/components';
import Modal from '$lib/components/modal.svelte';
import { Dependencies } from '$lib/constants';
import { Link } from '$lib/elements';
import {
Button,
Form,
InputChoice,
InputEmail,
InputPassword,
InputText
} from '$lib/elements/forms';
import { logout } from '$lib/helpers/logout';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { user } from '$lib/stores/user';
import { isCloud } from '$lib/system';
import { redirectTo } from '$routes/store';
import { ID, OAuthProvider } from '@appwrite.io/console';
import { Layout } from '@appwrite.io/pink-svelte';
import { Layout, Spinner, Typography } from '@appwrite.io/pink-svelte';
import { onMount } from 'svelte';
export let data;
onMount(async () => {
//check if user has access
//if not, redirect to login
let state: 'register' | 'login' | 'authenticated' = 'login';
let hasAccess = false;
let mail: string;
let pass: string;
let terms = false;
let name: string;
onMount(() => {
if (data?.account?.$id) {
//Check if they have access
state = 'authenticated';
}
//simulate login
setTimeout(() => {
goto(`${base}/auth/preview/access`);
}, 3000);
});
async function login() {
try {
await sdk.forConsole.account.createEmailPasswordSession(mail, pass);
await invalidate(Dependencies.ACCOUNT);
trackEvent(Submit.AccountLogin);
if ($user) {
addNotification({
type: 'success',
message: 'Successfully logged in.'
});
}
if ($redirectTo) {
window.location.href = $redirectTo;
return;
}
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AccountLogin);
}
}
async function register() {
try {
await sdk.forConsole.account.create(ID.unique(), mail, pass, name ?? '');
await sdk.forConsole.account.createEmailPasswordSession(mail, pass);
if ($redirectTo) {
window.location.href = $redirectTo;
return;
}
await invalidate(Dependencies.ACCOUNT);
trackEvent(Submit.AccountCreate);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AccountCreate);
}
}
function onGithubAuth() {
let url = window.location.origin;
if ($page.url.searchParams) {
const redirect = $page.url.searchParams.get('redirect');
$page.url.searchParams.delete('redirect');
if (redirect) {
url = `${redirect}${$page.url.search}`;
} else {
url = `${base}${$page.url.search ?? ''}`;
}
}
sdk.forConsole.account.createOAuth2Session(
OAuthProvider.Github,
window.location.origin + url,
window.location.origin,
['read:user', 'user:email']
);
}
async function handleSubmit() {
if (state === 'login') {
await login();
} else if (state === 'register') {
await register();
}
}
async function requestAccess() {
//TODO: Implement request access
}
$: console.log(data);
</script>
<Modal
title={state === 'authenticated' ? 'Request access' : 'Sign up or log in to view this preview'}
dismissible={false}
show
hideFooter={state !== 'authenticated'}>
<Form onSubmit={handleSubmit}>
<Layout.Stack gap="l">
{#if state === 'login'}
<InputEmail
id="email"
label="Email"
placeholder="Email"
autofocus
required
bind:value={mail} />
<InputPassword
id="password"
label="Password"
placeholder="Password"
required
meter={false}
showPasswordButton
bind:value={pass} />
<Button size="m" fullWidth submit>Sign in</Button>
{#if isCloud}
<span class="with-separators eyebrow-heading-3">or</span>
<Button size="m" secondary fullWidth on:click={onGithubAuth}>
<span class="icon-github" aria-hidden="true" />
<span class="text">Sign in with GitHub</span>
</Button>
{/if}
<Button size="m" text fullWidth on:click={() => (state = 'register')}>
Sign up instead
</Button>
{:else if state === 'register'}
<InputText
id="name"
label="Name"
placeholder="Your name"
autofocus
required
bind:value={name} />
<InputEmail
id="email"
label="Email"
placeholder="Email"
required
bind:value={mail} />
<InputPassword
id="password"
label="Password"
placeholder="Password"
required
meter={false}
showPasswordButton
bind:value={pass} />
<InputChoice required value={terms} id="terms" label="terms" showLabel={false}>
By registering, you agree that you have read, understand, and acknowledge our <a
class="link"
href="https://appwrite.io/privacy"
target="_blank"
rel="noopener noreferrer">
Privacy Policy</a>
and accept our
<a
class="link"
href="https://appwrite.io/terms"
target="_blank"
rel="noopener noreferrer">General Terms of Use</a
>.</InputChoice>
<Button size="m" fullWidth submit>Sign up</Button>
{#if isCloud}
<span class="with-separators eyebrow-heading-3">or</span>
<Button size="m" secondary fullWidth on:click={onGithubAuth}>
<span class="icon-github" aria-hidden="true" />
<span class="text">Continue with GitHub</span>
</Button>
{/if}
<Button size="m" text fullWidth on:click={() => (state = 'login')}>
Sign in instead
</Button>
{:else if state === 'authenticated'}
<Card isTile>
<Layout.Stack gap="xxs">
<p>
You're signed in as <b>{data.account.email}</b>
</p>
<Link
size="s"
on:click={() => {
logout();
state = 'login';
}}>Swith account</Link>
</Layout.Stack>
</Card>
{/if}
</Layout.Stack>
</Form>
<svelte:head>
<title>Preview - Appwrite</title>
</svelte:head>
<svelte:fragment slot="footer">
<Button>Request access</Button>
</svelte:fragment>
</Modal>
<Layout.Stack alignItems="center" justifyContent="center">
<Layout.Stack direction="row" alignItems="center" justifyContent="center">
<Spinner size="l" />
<Typography.Text>Authenticating...</Typography.Text>
</Layout.Stack>
<Typography.Title>Please wait while we authenticate you</Typography.Title>
</Layout.Stack>
+10 -3
View File
@@ -1,7 +1,14 @@
import { base } from '$app/paths';
import { redirect } from '@sveltejs/kit';
export const load = async ({ parent }) => {
const { account } = await parent();
return {
account
};
if (!account) {
redirect(303, `${base}/auth/preview/access`);
} else {
return {
account
};
}
};
@@ -0,0 +1,245 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Card } from '$lib/components';
import Modal from '$lib/components/modal.svelte';
import { Dependencies } from '$lib/constants';
import { Link } from '$lib/elements';
import {
Button,
Form,
InputChoice,
InputEmail,
InputPassword,
InputText
} from '$lib/elements/forms';
import { logout } from '$lib/helpers/logout';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { user } from '$lib/stores/user';
import { isCloud } from '$lib/system';
import { redirectTo } from '$routes/store';
import { ID, OAuthProvider } from '@appwrite.io/console';
import { Layout } from '@appwrite.io/pink-svelte';
import { onMount } from 'svelte';
import BGDark from './bg_dark.jpg';
import BGLight from './bg_light.jpg';
import { app } from '$lib/stores/app.js';
export let data;
let state: 'register' | 'login' | 'authenticated' = 'login';
let mail: string;
let pass: string;
let terms = false;
let name: string;
onMount(() => {
if (data?.account?.$id) {
state = 'authenticated';
}
});
async function login() {
try {
await sdk.forConsole.account.createEmailPasswordSession(mail, pass);
await invalidate(Dependencies.ACCOUNT);
trackEvent(Submit.AccountLogin);
if ($user) {
addNotification({
type: 'success',
message: 'Successfully logged in.'
});
}
if ($redirectTo) {
window.location.href = $redirectTo;
return;
}
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AccountLogin);
}
}
async function register() {
try {
await sdk.forConsole.account.create(ID.unique(), mail, pass, name ?? '');
await sdk.forConsole.account.createEmailPasswordSession(mail, pass);
if ($redirectTo) {
window.location.href = $redirectTo;
return;
}
await invalidate(Dependencies.ACCOUNT);
trackEvent(Submit.AccountCreate);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AccountCreate);
}
}
function onGithubAuth() {
let url = window.location.origin;
if ($page.url.searchParams) {
const redirect = $page.url.searchParams.get('redirect');
$page.url.searchParams.delete('redirect');
if (redirect) {
url = `${redirect}${$page.url.search}`;
} else {
url = `${base}${$page.url.search ?? ''}`;
}
}
sdk.forConsole.account.createOAuth2Session(
OAuthProvider.Github,
window.location.origin + url,
window.location.origin,
['read:user', 'user:email']
);
}
async function handleSubmit() {
if (state === 'login') {
await login();
} else if (state === 'register') {
await register();
}
}
async function requestAccess() {
//TODO: Implement request access
}
$: console.log(data);
</script>
{#if $app.themeInUse === 'dark'}
<img class="preview-img" src={BGDark} alt="background" aria-hidden="true" />
{:else}
<img class="preview-img" src={BGLight} alt="background" aria-hidden="true" />
{/if}
<svelte:head>
<title>Preview access - Appwrite</title>
</svelte:head>
<Modal
title={state === 'authenticated' ? 'Request access' : 'Sign up or log in to view this preview'}
dismissible={false}
show
hideFooter={state !== 'authenticated'}>
<Form onSubmit={handleSubmit}>
<Layout.Stack gap="l">
{#if state === 'login'}
<InputEmail
id="email"
label="Email"
placeholder="Email"
autofocus
required
bind:value={mail} />
<InputPassword
id="password"
label="Password"
placeholder="Password"
required
bind:value={pass} />
<Button size="m" fullWidth submit>Sign in</Button>
{#if isCloud}
<span class="with-separators eyebrow-heading-3">or</span>
<Button size="m" secondary fullWidth on:click={onGithubAuth}>
<span class="icon-github" aria-hidden="true" />
<span class="text">Sign in with GitHub</span>
</Button>
{/if}
<Button size="m" text fullWidth on:click={() => (state = 'register')}>
Sign up instead
</Button>
{:else if state === 'register'}
<InputText
id="name"
label="Name"
placeholder="Your name"
autofocus
required
bind:value={name} />
<InputEmail
id="email"
label="Email"
placeholder="Email"
required
bind:value={mail} />
<InputPassword
id="password"
label="Password"
placeholder="Password"
required
bind:value={pass} />
<InputChoice required value={terms} id="terms" label="terms" showLabel={false}>
By registering, you agree that you have read, understand, and acknowledge our <a
class="link"
href="https://appwrite.io/privacy"
target="_blank"
rel="noopener noreferrer">
Privacy Policy</a>
and accept our
<a
class="link"
href="https://appwrite.io/terms"
target="_blank"
rel="noopener noreferrer">General Terms of Use</a
>.</InputChoice>
<Button size="m" fullWidth submit>Sign up</Button>
{#if isCloud}
<span class="with-separators eyebrow-heading-3">or</span>
<Button size="m" secondary fullWidth on:click={onGithubAuth}>
<span class="icon-github" aria-hidden="true" />
<span class="text">Continue with GitHub</span>
</Button>
{/if}
<Button size="m" text fullWidth on:click={() => (state = 'login')}>
Sign in instead
</Button>
{:else if state === 'authenticated'}
<Card isTile>
<Layout.Stack gap="xxs">
<p>
You're signed in as <b>{data.account.email}</b>
</p>
<Link
size="s"
on:click={() => {
logout();
state = 'login';
}}>Swith account</Link>
</Layout.Stack>
</Card>
{/if}
</Layout.Stack>
</Form>
<svelte:fragment slot="footer">
<Button on:click={requestAccess}>Request access</Button>
</svelte:fragment>
</Modal>
<style lang="scss">
.preview-img {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: top left;
}
</style>
@@ -0,0 +1,7 @@
export const load = async ({ parent }) => {
const { account } = await parent();
return {
account
};
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB