Merge branch 'main' into availability-sla
@@ -1,4 +1,4 @@
|
||||
FROM node:20-bullseye as base
|
||||
FROM node:20-bullseye AS base
|
||||
|
||||
ARG PUBLIC_APPWRITE_ENDPOINT
|
||||
ENV PUBLIC_APPWRITE_ENDPOINT ${PUBLIC_APPWRITE_ENDPOINT}
|
||||
@@ -60,13 +60,13 @@ COPY pnpm-lock.yaml pnpm-lock.yaml
|
||||
RUN npm i -g corepack@latest
|
||||
RUN corepack enable
|
||||
|
||||
FROM base as build
|
||||
FROM base AS build
|
||||
|
||||
COPY . .
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
RUN NODE_OPTIONS=--max_old_space_size=16384 pnpm run build
|
||||
|
||||
FROM base as final
|
||||
FROM base AS final
|
||||
|
||||
# Install fontconfig
|
||||
COPY ./local-fonts /usr/share/fonts
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@tailwindcss/oxide",
|
||||
"core-js",
|
||||
"esbuild",
|
||||
"sharp",
|
||||
|
||||
@@ -59,7 +59,7 @@ export type TrackEventArgs = { name: string; data?: object };
|
||||
export const trackEvent = (eventArgs?: string | TrackEventArgs): void => {
|
||||
if (!eventArgs || ENV.TEST) return;
|
||||
|
||||
const path = page.route.id ?? '';
|
||||
const path = page.route.id?.replace(/\(([^()]*)\)/g, '') ?? '';
|
||||
const name = typeof eventArgs === 'string' ? eventArgs : eventArgs.name;
|
||||
const data = typeof eventArgs === 'string' ? { path } : { ...eventArgs.data, path };
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { getCodeHtml, type Language } from '$lib/utils/code';
|
||||
import { copy } from '$lib/utils/copy';
|
||||
import { platformMap } from '$lib/utils/references';
|
||||
import { writable } from 'svelte/store';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
interface Props {
|
||||
selected?: Language;
|
||||
@@ -14,18 +14,26 @@
|
||||
|
||||
let { selected = $bindable('js'), data = [], width = null, height = null }: Props = $props();
|
||||
|
||||
let snippets = $derived(writable(new Set(data.map((d) => d.language))));
|
||||
const getSnippets = () => {
|
||||
return snippets;
|
||||
};
|
||||
const snippets = $derived(new SvelteSet(data.map((d) => d.language)));
|
||||
const content = $derived(data.find((d) => d.language === selected)?.content ?? '');
|
||||
const platform = $derived(data.find((d) => d.language === selected)?.platform ?? '');
|
||||
const result = $derived(
|
||||
getCodeHtml({
|
||||
content,
|
||||
language: selected ?? 'sh',
|
||||
withLineNumbers: true
|
||||
})
|
||||
);
|
||||
const options = $derived(
|
||||
Array.from(snippets).map((language) => ({
|
||||
value: language,
|
||||
label: platformMap[language]
|
||||
}))
|
||||
);
|
||||
|
||||
let content = $derived(data.find((d) => d.language === selected)?.content ?? '');
|
||||
|
||||
let platform = $derived(data.find((d) => d.language === selected)?.platform ?? '');
|
||||
|
||||
getSnippets().subscribe((n) => {
|
||||
if (selected === null && n.size > 0) {
|
||||
selected = Array.from(n)[0] as Language;
|
||||
$effect(() => {
|
||||
if (selected === null && snippets.size > 0) {
|
||||
selected = Array.from(snippets)[0] as Language;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -35,9 +43,7 @@
|
||||
} as const;
|
||||
type CopyStatusType = keyof typeof CopyStatus;
|
||||
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||
|
||||
let copyText = $state<CopyStatusValue>(CopyStatus.Copy);
|
||||
|
||||
let copyText: CopyStatusValue = $state(CopyStatus.Copy);
|
||||
async function handleCopy() {
|
||||
await copy(content);
|
||||
|
||||
@@ -46,28 +52,13 @@
|
||||
copyText = CopyStatus.Copy;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
let result = $derived(
|
||||
getCodeHtml({
|
||||
content,
|
||||
language: selected ?? 'sh',
|
||||
withLineNumbers: true
|
||||
})
|
||||
);
|
||||
let options = $derived(
|
||||
Array.from($snippets).map((language) => ({
|
||||
value: language,
|
||||
label: platformMap[language]
|
||||
}))
|
||||
);
|
||||
</script>
|
||||
|
||||
<section
|
||||
class="dark web-code-snippet mx-auto w-full lg:!max-w-[90vw]"
|
||||
aria-label="code-snippet panel"
|
||||
style={`width: ${width ? width / 16 + 'rem' : 'inherit'}; height: ${
|
||||
height ? height / 16 + 'rem' : 'inherit'
|
||||
}`}
|
||||
style:width={width ? width / 16 + 'rem' : 'inherit'}
|
||||
style:height={height ? height / 16 + 'rem' : 'inherit'}
|
||||
>
|
||||
<header class="web-code-snippet-header">
|
||||
<div class="web-code-snippet-header-start">
|
||||
@@ -79,9 +70,9 @@
|
||||
</div>
|
||||
<div class="web-code-snippet-header-end">
|
||||
<ul class="buttons-list flex gap-3">
|
||||
{#if $snippets.entries.length}
|
||||
{#if snippets.size}
|
||||
<li class="buttons-list-item flex self-center">
|
||||
<Select bind:value={selected} bind:options />
|
||||
<Select bind:value={selected} {options} />
|
||||
</li>
|
||||
{/if}
|
||||
<li class="buttons-list-item" style="padding-inline-start: 13px">
|
||||
@@ -104,7 +95,7 @@
|
||||
</header>
|
||||
<div
|
||||
class="web-code-snippet-content overflow-auto"
|
||||
style={`height: ${height ? height / 16 + 'rem' : 'inherit'}`}
|
||||
style:height={height ? height / 16 + 'rem' : 'inherit'}
|
||||
>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html result}
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
{plan.description}
|
||||
</p>
|
||||
<Button
|
||||
href={plan.buttonLink}
|
||||
event={plan.eventName}
|
||||
variant={plan.buttonVariant}
|
||||
class="w-full! flex-3 self-end md:w-fit"
|
||||
@@ -135,29 +136,16 @@
|
||||
flex-basis: 5rem !important;
|
||||
}
|
||||
|
||||
.web-strip-plans-item-wrapper {
|
||||
gap: 2.65rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) and (max-width: 1224px) {
|
||||
.web-strip-plans-info {
|
||||
flex-basis: 1rem !important;
|
||||
}
|
||||
|
||||
.web-strip-plans-item-wrapper {
|
||||
gap: 1.25rem !important;
|
||||
inline-size: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.web-strip-plans-info {
|
||||
flex-basis: 3rem !important;
|
||||
}
|
||||
|
||||
.web-strip-plans-item-wrapper {
|
||||
gap: 1.25rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.web-pre-footer-bg {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { classNames } from '$lib/utils/classnames';
|
||||
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
|
||||
import { cva, type VariantProps } from 'cva';
|
||||
import InlineTag from '../ui/InlineTag.svelte';
|
||||
import InlineTag from '../ui/inline-tag.svelte';
|
||||
|
||||
const button = cva(
|
||||
[
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { fade, scale } from 'svelte/transition';
|
||||
import { createDialog, melt } from '@melt-ui/svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
type Props = {
|
||||
url: string;
|
||||
@@ -12,7 +11,7 @@
|
||||
};
|
||||
|
||||
const {
|
||||
elements: { portalled, trigger, content, overlay },
|
||||
elements: { trigger, content, overlay },
|
||||
states: { open }
|
||||
} = createDialog({
|
||||
forceVisible: true,
|
||||
@@ -27,14 +26,9 @@
|
||||
{@render children()}
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class="contents cursor-pointer"
|
||||
onclick={() => {
|
||||
if (browser && window) window.open(url, '_blank');
|
||||
}}
|
||||
>
|
||||
<a href={url} target="_blank" rel="noopener noreferrer" class="contents cursor-pointer">
|
||||
{@render children()}
|
||||
</button>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if $open}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils/classnames';
|
||||
import type { SvelteHTMLElements } from 'svelte/elements';
|
||||
import type { SVGAttributes } from 'svelte/elements';
|
||||
import type { IconType } from './types';
|
||||
|
||||
type Props = SvelteHTMLElements['svg'] & {
|
||||
type Props = SVGAttributes<SVGElement> & {
|
||||
class?: string;
|
||||
name: IconType;
|
||||
name?: IconType;
|
||||
};
|
||||
|
||||
const {
|
||||
xmlns = 'http://www.w3.org/2000/svg',
|
||||
viewBox = '0 0 24 24',
|
||||
|
||||
@@ -59,13 +59,13 @@ export type SocialShareOption = {
|
||||
type: 'link' | 'copy';
|
||||
};
|
||||
|
||||
export type IntegrationCategory = {
|
||||
export type SearchableCategory = {
|
||||
slug: string;
|
||||
heading: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export const integrationCategoryDescriptions: IntegrationCategory[] = [
|
||||
export const integrationCategoryDescriptions: SearchableCategory[] = [
|
||||
{
|
||||
slug: 'ai',
|
||||
heading: 'AI',
|
||||
@@ -118,6 +118,14 @@ export const integrationCategoryDescriptions: IntegrationCategory[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export const partnerCategoryDescriptions: SearchableCategory[] = [
|
||||
{
|
||||
slug: 'agency',
|
||||
heading: 'Agency',
|
||||
description: 'Agencies that build software for their clients using Appwrite'
|
||||
}
|
||||
];
|
||||
|
||||
export const socialSharingOptions: Array<SocialShareOption> = [
|
||||
{
|
||||
icon: 'web-icon-x',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts" module>
|
||||
import { navigating } from '$app/stores';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export type DocsLayoutVariant = 'default' | 'expanded' | 'two-side-navs';
|
||||
@@ -40,8 +39,6 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { Search, IsLoggedIn } from '$lib/components';
|
||||
import { isMac } from '$lib/utils/platform';
|
||||
import { getContext, setContext } from 'svelte';
|
||||
@@ -49,6 +46,7 @@
|
||||
import { page } from '$app/state';
|
||||
import { getAppwriteDashboardUrl } from '$lib/utils/dashboard';
|
||||
import { Button, Icon, InlineTag } from '$lib/components/ui';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
|
||||
interface Props {
|
||||
variant?: DocsLayoutVariant;
|
||||
@@ -70,7 +68,7 @@
|
||||
$layoutState.currentVariant = variant;
|
||||
});
|
||||
|
||||
navigating.subscribe(() => {
|
||||
afterNavigate(() => {
|
||||
layoutState.update((n) => ({
|
||||
...n,
|
||||
showReferences: false,
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
rel="noopener noreferrer"
|
||||
class="web-u-inline-width-100-percent-mobile"
|
||||
>
|
||||
<Icon class="star" aria-hidden="true"></Icon>
|
||||
<Icon class="star" aria-hidden />
|
||||
<span class="text">Star on GitHub</span>
|
||||
<InlineTag>{SOCIAL_STATS.GITHUB.STAT}</InlineTag>
|
||||
</Button>
|
||||
|
||||
@@ -65,6 +65,7 @@ export const Platform = {
|
||||
|
||||
type PlatformType = typeof Platform;
|
||||
export type Platform = (typeof Platform)[keyof typeof Platform];
|
||||
export const VALID_PLATFORMS = new Set(Object.values(Platform));
|
||||
|
||||
export const Framework = {
|
||||
NextJs: 'Next.js',
|
||||
@@ -154,9 +155,17 @@ export const preferredVersion = writable<Version | null>(
|
||||
globalThis?.localStorage?.getItem('preferredVersion') as Version
|
||||
);
|
||||
|
||||
export const preferredPlatform = writable<Platform>(
|
||||
(globalThis?.localStorage?.getItem('preferredPlatform') ?? 'client-web') as Platform
|
||||
);
|
||||
function getInitialPlatform(): Platform {
|
||||
const stored = globalThis?.localStorage?.getItem('preferredPlatform') ?? Platform.ClientWeb;
|
||||
// return if this platform is valid
|
||||
if (VALID_PLATFORMS.has(stored as Platform)) {
|
||||
return stored as Platform;
|
||||
} else {
|
||||
return Platform.ClientWeb;
|
||||
}
|
||||
}
|
||||
|
||||
export const preferredPlatform = writable<Platform>(getInitialPlatform());
|
||||
|
||||
if (browser) {
|
||||
preferredVersion.subscribe((value) => {
|
||||
@@ -164,6 +173,9 @@ if (browser) {
|
||||
});
|
||||
|
||||
preferredPlatform.subscribe((value) => {
|
||||
if (value) globalThis?.localStorage?.setItem('preferredPlatform', value);
|
||||
// only save the ones for which we have api references.
|
||||
if (value && VALID_PLATFORMS.has(value)) {
|
||||
globalThis?.localStorage?.setItem('preferredPlatform', value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
172
src/markdoc/layouts/Partner.svelte
Normal file
@@ -0,0 +1,172 @@
|
||||
<script lang="ts">
|
||||
import FooterNav from '$lib/components/FooterNav.svelte';
|
||||
import MainFooter from '$lib/components/MainFooter.svelte';
|
||||
import { Main } from '$lib/layouts';
|
||||
import { DEFAULT_HOST } from '$lib/utils/metadata';
|
||||
|
||||
import { classNames } from '$lib/utils/classnames';
|
||||
import type { Partner } from '$routes/partners/catalog/+page';
|
||||
import ContactPartner from '$routes/partners/catalog/(components)/contact-partner.svelte';
|
||||
|
||||
export let title: Partner['title'];
|
||||
export let partnerLevel: Partner['partnerLevel'];
|
||||
export let category: Partner['category'];
|
||||
export let description: Partner['description'];
|
||||
export let cover: Partner['cover'];
|
||||
export let capabilities: Partner['capabilities'];
|
||||
export let frameworks: Partner['frameworks'];
|
||||
export let regions: Partner['regions'];
|
||||
export let languages: Partner['languages'];
|
||||
export let website: Partner['website'];
|
||||
|
||||
const ogImage = DEFAULT_HOST + cover;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<!-- Titles -->
|
||||
<title>{title}</title>
|
||||
<meta property="og:title" content={title} />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<!-- Description -->
|
||||
<meta name="description" content={description} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<!-- Image -->
|
||||
<meta property="og:image" content={ogImage} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta name="twitter:image" content={ogImage} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
</svelte:head>
|
||||
|
||||
<Main>
|
||||
<div
|
||||
class={classNames(
|
||||
'grid-bg border-smooth relative flex items-center border-b px-5 py-28 lg:px-8 xl:px-16',
|
||||
'before:from-accent/20 before:absolute before:inset-0 before:-z-1 before:bg-linear-to-tr before:via-transparent before:via-40% before:to-transparent'
|
||||
)}
|
||||
>
|
||||
<div class="relative container w-full pb-0">
|
||||
<div class="flex flex-col gap-7">
|
||||
<a href="/partners" class="text-caption text-primary group flex gap-2">
|
||||
<span class="web-icon-arrow-left transition group-hover:-translate-x-1" />
|
||||
Back to Partners Catalog
|
||||
</a>
|
||||
<h1 class="text-headline font-aeonik-pro text-primary">{title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="py-10">
|
||||
<div class="container">
|
||||
<article class="flex flex-col gap-10 md:gap-14">
|
||||
<div class="grid grid-cols-1 gap-10 md:grid-cols-12 md:gap-x-14">
|
||||
<div class="md:col-span-7">
|
||||
<div class="web-article">
|
||||
<div class="web-article-content">
|
||||
<slot />
|
||||
</div>
|
||||
<ContactPartner />
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:col-span-5">
|
||||
<h2 class="text-label text-primary font-aeonik-pro">About {title}</h2>
|
||||
<dl class="divide-smooth sticky top-32 mt-10 flex flex-col gap-7 divide-y">
|
||||
<div class="flex flex-col justify-between gap-7 pb-7">
|
||||
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
|
||||
Frameworks
|
||||
</dt>
|
||||
<dd class="flex flex-wrap gap-2">
|
||||
{#each frameworks as framework}
|
||||
<div
|
||||
class="text-primary text-caption bg-smooth rounded-full px-3 py-1"
|
||||
>
|
||||
{framework}
|
||||
</div>
|
||||
{/each}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-between gap-7 pb-7">
|
||||
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
|
||||
Capabilities
|
||||
</dt>
|
||||
<dd class="flex flex-wrap gap-2">
|
||||
{#each capabilities as capability}
|
||||
<div
|
||||
class="text-primary text-caption bg-smooth rounded-full px-3 py-1"
|
||||
>
|
||||
{capability}
|
||||
</div>
|
||||
{/each}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-7 pb-7">
|
||||
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
|
||||
Category
|
||||
</dt>
|
||||
<dd class="text-primary text-caption">{category}</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-7 pb-7">
|
||||
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
|
||||
Website
|
||||
</dt>
|
||||
<dd
|
||||
class="text-primary text-caption font-medium underline underline-offset-4"
|
||||
>
|
||||
{website}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-7 pb-7">
|
||||
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
|
||||
Partner Level
|
||||
</dt>
|
||||
<dd>
|
||||
<div
|
||||
class="text-primary text-caption rounded bg-white/24 px-2 py-0.5"
|
||||
>
|
||||
{partnerLevel}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-8 pb-7">
|
||||
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
|
||||
Regions
|
||||
</dt>
|
||||
<dd class="text-primary text-caption">
|
||||
{regions.join(', ')}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-between gap-7 pb-7">
|
||||
<dt class="text-micro font-aeonik-fono tracking-loose uppercase">
|
||||
Languages
|
||||
</dt>
|
||||
<dd class="flex flex-wrap gap-2">
|
||||
{#each languages as language}
|
||||
<div
|
||||
class="text-primary text-caption bg-smooth rounded-full px-3 py-1"
|
||||
>
|
||||
{language}
|
||||
</div>
|
||||
{/each}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 overflow-hidden py-10">
|
||||
<div class="container">
|
||||
<FooterNav />
|
||||
<MainFooter />
|
||||
</div>
|
||||
</div>
|
||||
</Main>
|
||||
@@ -81,5 +81,5 @@
|
||||
class:web-snap-location-references={id && inReferences}
|
||||
class="{headingClass} text-primary scroll-m-32 font-medium"
|
||||
>
|
||||
<a href={`#${href}`} class="">{@render children()}</a>
|
||||
<a href={`#${id ?? slugify(element?.innerText ?? '')}`} class="">{@render children()}</a>
|
||||
</svelte:element>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="flex flex-col gap-2 md:flex-row">
|
||||
<div class="z-[1] flex flex-col gap-2 md:flex-row">
|
||||
<Button href={url} class="max-sm:w-full!">{cta}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -98,12 +98,12 @@
|
||||
on:emblaInit={onEmblaInit}
|
||||
>
|
||||
<div class="embla__container flex">
|
||||
{#each events as _}
|
||||
{#each events as { poster, title }}
|
||||
<div
|
||||
class="embla__slide bg-card/90 mr-4 min-w-0 [flex:0_0_33%] items-center rounded-lg p-4"
|
||||
>
|
||||
<img src={_.poster} class="m-auto rounded-t" />
|
||||
<h3 class="mt-0.5 text-base font-medium">{_.title}</h3>
|
||||
<img alt={title} src={poster} class="m-auto rounded-t" />
|
||||
<h3 class="mt-0.5 text-base font-medium">{title}</h3>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { APPWRITE_DB_INIT_ID, APPWRITE_COL_INIT_ID } from '$env/static/private';
|
||||
import { DOMParser, parseHTML } from 'linkedom';
|
||||
|
||||
import type { TicketData } from './tickets';
|
||||
import { APPWRITE_COL_INIT_ID, APPWRITE_DB_INIT_ID } from '$env/static/private';
|
||||
import { parseHTML } from 'linkedom';
|
||||
import { z } from 'zod';
|
||||
import { createInitServerClient } from './appwrite';
|
||||
import type { TicketData } from './tickets';
|
||||
|
||||
const contributionsSchema = z.array(z.array(z.number()));
|
||||
export type ContributionsMatrix = z.infer<typeof contributionsSchema>;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { redirect, type Action, type Actions } from '@sveltejs/kit';
|
||||
import { redirect, type Actions } from '@sveltejs/kit';
|
||||
import { getTicketByUser } from './(utils)/tickets';
|
||||
import { OAuthProvider } from 'appwrite';
|
||||
import { Account, Client } from 'node-appwrite';
|
||||
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_INIT_ID } from '$env/static/public';
|
||||
import { loginGithub } from './(utils)/auth';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { getTicketDocByUsername } from '../../(utils)/tickets';
|
||||
import { error, redirect, type Actions } from '@sveltejs/kit';
|
||||
import { getTicketContributions } from '../../(utils)/contributions';
|
||||
import { OAuthProvider } from 'appwrite';
|
||||
import { Account, Client } from 'node-appwrite';
|
||||
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_INIT_ID } from '$env/static/public';
|
||||
import { loginGithub } from '../../(utils)/auth';
|
||||
|
||||
export const ssr = true;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{
|
||||
logo: LangX,
|
||||
headline: 'LangX handled millions of requests using Appwrite',
|
||||
blurb: 'With its comprehensive suite of services Appwrite emerged as an ideal choice for my needs.',
|
||||
blurb: 'With its comprehensive suite of services, Appwrite emerged as an ideal choice for my needs.',
|
||||
name: 'Xue',
|
||||
title: 'Founder at LangX',
|
||||
avatar: '/images/testimonials/xue.webp',
|
||||
@@ -28,10 +28,10 @@
|
||||
},
|
||||
{
|
||||
logo: KCollect,
|
||||
headline: 'K-collect reduced infrastructure costs by 700%',
|
||||
headline: 'K-Collect reduced infrastructure costs by 700%',
|
||||
blurb: 'A major impact that Appwrite made was the amount of time and stress saved.',
|
||||
name: "Ryan O'connor",
|
||||
title: 'Founder at K-collect',
|
||||
name: "Ryan O'Connor",
|
||||
title: 'Founder at K-Collect',
|
||||
avatar: '/images/testimonials/ryan.png',
|
||||
url: '/blog/post/customer-stories-kcollect'
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<Map theme="light" />
|
||||
<Scale
|
||||
testimonial={{
|
||||
name: 'Ryan O’Conner',
|
||||
name: 'Ryan O’Connor',
|
||||
title: 'Founder',
|
||||
company: 'K-Collect',
|
||||
image: '/images/testimonials/ryan-oconner-testimonial.png'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layout: author
|
||||
name: Arman
|
||||
slug: arman
|
||||
role: Frontend Developer
|
||||
role: Product Engineer
|
||||
bio: In ♥ with Svelte and Vue, currently working at Appwrite
|
||||
avatar: /images/avatars/arman.png
|
||||
twitter: https://twitter.com/NikNotNik
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layout: author
|
||||
name: Binyamin Yawitz
|
||||
slug: binyamin-yawitz
|
||||
role: Software Engineer
|
||||
role: Platform Engineer
|
||||
bio: Developer with multilingual skills and systems experience.
|
||||
avatar: /images/avatars/binyamin.png
|
||||
github: https://github.com/byawitz/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layout: author
|
||||
slug: bradley-schofield
|
||||
name: Bradley Schofield
|
||||
role: Software Engineer
|
||||
role: Platform Engineer
|
||||
bio: Integrating platforms and managing data in Appwrite.
|
||||
avatar: /images/avatars/bradley.png
|
||||
twitter: https://x.com/ionicisere
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layout: author
|
||||
slug: damodar-lohani
|
||||
name: Damodar Lohani
|
||||
role: Engineer
|
||||
role: Platform Engineer
|
||||
bio: Author, mentor, trainer, and tech consultant.
|
||||
avatar: /images/avatars/damodar.png
|
||||
twitter: https://twitter.com/lohanidamodar
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layout: author
|
||||
slug: luke-silver
|
||||
name: Luke B. Silver
|
||||
role: Software Engineer
|
||||
role: Platform Engineer
|
||||
bio: Building integrations with and on Appwrite
|
||||
avatar: /images/avatars/luke.png
|
||||
twitter: https://twitter.com/lukebsilver
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layout: author
|
||||
slug: matej-baco
|
||||
name: Matej Bačo
|
||||
role: Software Engineer
|
||||
role: Engineering Lead
|
||||
bio: Always building something cool with Appwrite
|
||||
avatar: /images/avatars/matej.png
|
||||
twitter: https://twitter.com/_meldiron
|
||||
|
||||
@@ -23,9 +23,15 @@ In simple words, open-source software is made available with a license that allo
|
||||
|
||||
Unlike traditional software, which is owned and controlled by a company, open-source products gives you full access to the code, so you can customize the software to meet your specific needs.
|
||||
|
||||
It’s more than just access to the code. We’ve seen too many cases where startups got burned by closed platforms.
|
||||
|
||||
A founder recently shared on [LinkedIn](https://www.linkedin.com/feed/update/urn:li:activity:7331362060750032896/) how their client lost $65,000 and their entire EdTech platform overnight. Just because the platform they were using filed for bankruptcy without warning. Fired all 1,500 employees. Locked every customer out. No access to code. No way to export data. No one to even reach out to.
|
||||
|
||||
That’s what happens when your infrastructure is not in your control. This is why open-source matters. Because with open-source, you’re not dependent on a single company’s fate. You can self-host. You can migrate. You can fork the code and keep building.
|
||||
|
||||
# Benefits of open-source software
|
||||
|
||||
While the huge benefit of an open-source product is the ability to customize it according to your needs, there are many other benefits to consider, like:
|
||||
While the huge benefit of an [open-source](https://opensource.org/) product is the ability to customize it according to your needs, there are many other benefits to consider, like:
|
||||
|
||||
## Cost savings
|
||||
Open-source products generally offer budget-friendly options for businesses of all sizes. Many open-source projects are free to get started and you can pay as you scale or need additional support, which could be a great option for budget-tight teams.
|
||||
@@ -44,34 +50,64 @@ Most open-source projects have self-hostable versions that allow you to host the
|
||||
# 10 open-source product alternatives
|
||||
|
||||
## 1. [n8n](https://n8n.io/)
|
||||
|
||||

|
||||
|
||||
**n8n** is an open-source alternative to **Zapier**. It allows users to automate workflows by connecting various apps and services, similar to Zapier, but with the added advantage of being fully customizable.
|
||||
|
||||
## 2. [Appsmith](https://www.appsmith.com/)
|
||||
|
||||

|
||||
|
||||
Appsmith is an open-source alternative to Retool. It enables you to build custom internal tools quickly by connecting to databases, APIs, and other services. It provides a drag-and-drop interface to design apps, making it easy to create dashboards and workflows.
|
||||
|
||||
## 3. [Documenso](https://documenso.com/)
|
||||
|
||||

|
||||
|
||||
Documenso is an open-source alternative to Docusign. It is a document management platform designed to help businesses organize, store, and collaborate on documents. It offers features like document indexing, searching, and version control, allowing teams to manage their files efficiently.
|
||||
|
||||
## 4. [Dub.co](https://dub.co/)
|
||||
|
||||

|
||||
|
||||
Dub.co is an open-source alternative to Bitly. It is a link management platform designed for modern marketing teams. It allows users to create branded short links using custom domains, track clicks with detailed analytics, and manage marketing campaigns efficiently.
|
||||
|
||||
## 5. [AppFlowy](https://appflowy.com/)
|
||||
|
||||

|
||||
|
||||
AppFlowy is an open-source alternative to Notion. It provides a flexible workspace for note-taking, project management, and collaboration with features like customizable templates, task tracking, and databases.
|
||||
|
||||
## 6. [WebStudio](https://webstudio.is/)
|
||||
WebStudio is an open-source alternative to WebFlow. An open-source platform for building and managing websites with a focus on simplicity and flexibility. It provides an intuitive drag-and-drop interface, allowing users to design websites without coding.
|
||||
## 6. [Webstudio](https://webstudio.is/)
|
||||
|
||||

|
||||
|
||||
Webstudio is an open-source alternative to Webflow. An open-source platform for building and managing websites with a focus on simplicity and flexibility. It provides an intuitive drag-and-drop interface, allowing users to design websites without coding.
|
||||
|
||||
## 7. [Typesense](https://typesense.org/)
|
||||
|
||||

|
||||
|
||||
Typesense is an open-source alternative to Algolia. A fast and relevant search engine built for developers. It allows you to easily integrate full-text search functionality into your applications with features like typo tolerance, filtering, faceting, and real-time indexing.
|
||||
|
||||
## 8. [PostHog](https://posthog.com/)
|
||||
|
||||

|
||||
|
||||
PostHog is an open-source alternative to Mixpanel. A product analytics platform designed to help businesses track user behavior and make data-driven decisions. It provides teams with tools that allow them to gain insights into how users interact with their products.
|
||||
|
||||
## 9. [Sentry](https://sentry.io/welcome/)
|
||||
|
||||

|
||||
|
||||
Sentry is an open-source alternative to Raygun. An error tracking and monitoring platform that helps developers identify and fix issues in their applications in real-time. It provides detailed error reports making it easier to diagnose and resolve bugs quickly.
|
||||
|
||||
## 10. [Appwrite](https://appwrite.io/)
|
||||
Appwrite is an open-source alternative to Firebase. A backend-as-a-service (BaaS) platform that simplifies the development of web and mobile applications. It provides developers with tools for user authentication, databases, file storage, and real-time capabilities, all through easy-to-use APIs.
|
||||
|
||||

|
||||
|
||||
Appwrite is an open-source alternative to Firebase, Vercel and Auth0. An all-in-one development platform that provides you with built-in backend infrastructure and hosting. It includes everything from auth, databases, and storage to serverless functions and real-time APIs, so you can focus on building, not wiring things together.
|
||||
|
||||
# Conclusion
|
||||
In conclusion, open-source products gives your startup the flexibility, control, and cost savings you need to scale and innovate.
|
||||
|
||||
@@ -9,6 +9,7 @@ author: veeresh-mulge
|
||||
callToAction: true
|
||||
unlisted: true
|
||||
category: tutorial
|
||||
call_to_action: true
|
||||
---
|
||||
|
||||
This has to be the best time in history to start a business. With so many advancements in AI tools and agents, it has never been this easy to build and scale your startup. The next breed of billion-dollar companies will be tiny teams backed by AI agents and workflows.
|
||||
@@ -39,7 +40,7 @@ MCP is just the standardized way of making this happen. Instead of every AI need
|
||||
|
||||
MCP was developed by Anthropic and released as an open standard in November 2024, making it a relatively new but rapidly growing solution.
|
||||
|
||||
We've covered everything you need to know about MCP in a recent blog, but let's dive into 5 MCP or AI agents startup opportunities you can start building today.
|
||||
We've covered everything you need to know about MCP in a recent [blog](https://appwrite.io/blog/post/what-is-mcp), but let's dive into 5 MCP or AI agents startup opportunities you can start building today.
|
||||
|
||||
# 1. MCP App Store
|
||||
Create an MCP APP Store, where users can browse and deploy various MCP servers. Think of it like an app store, but for MCP servers. Developers could look through different MCP servers, find the ones they need, and deploy them with just a few clicks.
|
||||
@@ -49,6 +50,8 @@ The idea is to simplify the process of integrating different services with LLMs,
|
||||
# 2. Incident Tracer
|
||||
When your app breaks, whether it's a bug or an outage, an MCP agent traces every log, commit, and Slack message to give you a full incident report in seconds. So, instead of manually digging through logs or trying to piece together what went wrong, the MCP agent automatically collects all the relevant information, creating a detailed report that helps you quickly understand the issue.
|
||||
|
||||
{% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%}
|
||||
|
||||
# 3. Task Assistant
|
||||
An MCP-powered agent that shadows founders, reading emails, meetings, tasks, and documents. It provides daily summaries and suggests the next best actions, acting like your personal Chief of Staff. This allows founders to stay focused on what matters while it keeps track of everything and ensures nothing slips through the cracks.
|
||||
|
||||
@@ -63,3 +66,8 @@ AIChangelog tracks everything every AI agent does and why, creating a detailed,
|
||||
MCP changes everything. Before, AI agents were isolated. Now, they connect, collaborate, and scale. This is a huge opportunity to build AI agents and create connected, intelligent solutions that automate tasks, streamline processes, and solve real-world problems.
|
||||
|
||||
The Appwrite's Startups Program can help you do that with the tools and support you need to create these AI-driven products. [Apply today](appwrite.io/startups) and start building.
|
||||
|
||||
# Further reading
|
||||
- [Appwrite MCP documentation](https://appwrite.io/docs/tooling/mcp?doFollow=true)
|
||||
- [What exactly is MCP, and why is it trending?](https://appwrite.io/blog/post/what-is-mcp)
|
||||
- [Anthropic MCP documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp)
|
||||
|
||||
@@ -133,6 +133,16 @@ Self-hosting support essentially means a developer can deploy the application on
|
||||
- Appwrite offers one-click setups for DigitalOcean, Gitpod, and Akamai Compute. Supabase offers the same for DigitalOcean and AWS.
|
||||
- Supabase features community-maintained helm charts to self-host using Kubernetes.
|
||||
|
||||
## Hosting
|
||||
|
||||
Appwrite offers native hosting as part of its all-in-one platform. That means you can develop, deploy, and scale your application all from a single platform. Supabase, while it provides backend services, does not include frontend hosting.
|
||||
|
||||
*Differences:*
|
||||
|
||||
- Appwrite includes built-in hosting for static and dynamic sites via Appwrite Sites. Supabase does not offer frontend hosting. You must be dependent on third-party tools like Vercel or Netlify to host your application.
|
||||
- Appwrite Sites supports server-side rendering out of the box.
|
||||
- With Appwrite, developers can manage everything from frontend and backend to environments, deployments, and monitoring from a single control panel, while Supabase requires using multiple services and dashboards, increasing complexity.
|
||||
|
||||
## Support
|
||||
|
||||
Any developer-first product, regardless of how good and simple it may be, will need a system for support in case its consumers face any issues. Both Appwrite and Supabase have developed support offerings to ensure their consumers can share feedback and get the necessary help.
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
layout: post
|
||||
title: Redesigning our homepage to reflect Appwrite’s new positioning
|
||||
description: A behind-the-scenes look at how we redesigned the Appwrite homepage to reflect our evolution from a BaaS to an all-in-one open-source development platform.
|
||||
date: 2025-06-11
|
||||
cover: /images/blog/appwrite-homepage-redesign/cover-image.png
|
||||
timeToRead: 08
|
||||
author: sara-kaandorp
|
||||
category: design
|
||||
featured: false
|
||||
callToAction: true
|
||||
---
|
||||
|
||||
At Appwrite, we’ve grown from a Backend-as-a-Service into an open-source, all-in-one development platform. With the introduction of our newest product, Sites, we expanded our offering beyond backend services to include hosting. This shift meant our homepage needed to do more, it had to communicate the full breadth of what Appwrite now offers.
|
||||
|
||||
# Phase 1: Setting the baseline
|
||||
|
||||
Before jumping into design changes, we wanted to understand how the homepage was performing. That meant:
|
||||
|
||||
- Recording baseline metrics of how our homepage was performing
|
||||
- Getting fresh insight into what kind of first impression our homepage was giving to developers who were new to Appwrite
|
||||
|
||||
We ran a qualitative survey with 50 respondents who closely matched our target audience, but who were new to Appwrite. Instead of asking existing community members who already have a mental model of what Appwrite is, we wanted to get fresh insight into the first impression of a developer with no bias. The results were insightful.
|
||||
|
||||
## What we heard:
|
||||
|
||||
- Positive: People liked the clean layout and visual design. Many said the structure was easy to follow.
|
||||
- Confusing: Our messaging wasn’t clear enough. Some thought Appwrite was a DevOps tool, others a CMS, or just another Firebase clone.
|
||||
|
||||
This validated what we suspected: while the design was polished, the story we told wasn’t fully landing.
|
||||
|
||||

|
||||
|
||||
# Phase 2: Iterating on the story
|
||||
|
||||

|
||||
|
||||
Based on those findings, we created a new homepage design in Figma and ran a second qualitative survey—this time on the proposed design with renewed messaging. The results were encouraging.
|
||||
|
||||

|
||||
|
||||
## What we improved:
|
||||
|
||||
- **Backend setup clarity**: More users grasped what Appwrite offers, though some still wanted more detail on how the setup works.
|
||||
- **Scalability**: Surfaced more often in feedback, indicating stronger visibility.
|
||||
- **Feature awareness**: Users showed deeper engagement with our capabilities.
|
||||
|
||||
# Phase 3: Final design
|
||||
|
||||
We simplified our messaging further and focused on showing—not just telling—what Appwrite offers. We:
|
||||
|
||||
- Added components that give a quick visual and conceptual overview of our offering, without requiring visitors to read everything. We honed in on different user behaviors: some people want a fast impression; others want to scroll deep. Our content now supports both. (This is backed by UX research—see Nielsen Norman Group's work on scanning vs. reading behaviors.)
|
||||
|
||||

|
||||
|
||||
- Repositioned Appwrite from just a BaaS to a complete platform with Sites, our hosting solution, clearly integrated into the story.
|
||||
|
||||
- Selected testimonials strategically—highlighting impact metrics like time saved, cost reduction, and scalability, to build credibility with both developers and founders.
|
||||
|
||||

|
||||
|
||||
While the main aim was to improve the story that we were trying to convey, we also further improved our visual language. We did a few iterations to further simplify our visuals and worked on an animation style that feels more natural. In our old page, we had animations based on scroll behavior that slightly hijacked user control—something developers disliked. The new animations and microinteractions make the page feel alive without taking away from the main purpose: understanding the story of Appwrite.
|
||||
|
||||

|
||||
|
||||
With all these changes, we moved into a final design iteration—the version that’s now live.
|
||||
|
||||
# What’s next
|
||||
|
||||
We'll continue to refine our new homepage. There are still headlines we want to test, feature explanations to expand on, and new ways we can illustrate how Appwrite works.
|
||||
|
||||
Redesigning a homepage is about crafting the clearest possible introduction to your product. And that process doesn't end with this launch.
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
---
|
||||
layout: post
|
||||
title: Build an offline AI chatbot with WebLLM and WebGPU
|
||||
description: Learn how to build an offline AI chatbot with WebLLM and WebGPU.
|
||||
date: 2025-06-10
|
||||
cover: /images/blog/chatbot-with-webllm-and-webgpu/cover.png
|
||||
timeToRead: 13
|
||||
author: ebenezer-don
|
||||
category: tutorial
|
||||
featured: false
|
||||
---
|
||||
|
||||
When you hear "LLM," you probably think of APIs, tokens, and cloud infrastructure. But what if we could remove the server completely? That your browser could download a model, run it on your device, and answer questions in real time, all using just JavaScript and GPU acceleration?
|
||||
|
||||
Local LLMs running inside the browser were nearly impossible just a year ago. But thanks to new technologies like WebLLM and WebGPU, you can now load a full language model into memory, run it on your device, and have a real-time conversation, all without a server.
|
||||
|
||||
In this guide, we'll build a local chatbot that runs entirely in the browser. No backend. No API keys. By the end, you should have a good understanding of [WebLLM](https://webllm.mlc.ai/) and [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API), and will have built an app that looks and functions like this:
|
||||
|
||||

|
||||
|
||||
You can also try out the app using this [live URL](https://ebenezerdon.github.io/simple-webllm-chat/).
|
||||
|
||||
To build this, we need to first understand two important pieces: **WebLLM** and **WebGPU**.
|
||||
|
||||
## What is WebLLM?
|
||||
|
||||
WebLLM is an open-source project from the team at MLC (Machine Learning Compiler). It lets you run language models directly in your browser tab using GPU acceleration. The models are compiled into formats that your browser can understand and execute, no need to send data to a server.
|
||||
|
||||
Why is this important?
|
||||
|
||||
- It keeps user data private
|
||||
- It reduces latency
|
||||
- It works offline after the model download
|
||||
- It removes the need for API costs or rate limits
|
||||
|
||||
Under the hood, WebLLM handles model loading, tokenization, execution, and streaming responses. It gives you a simple interface to load and chat with a model like LLaMA or Phi.
|
||||
|
||||
But WebLLM can't do it alone. It needs hardware access, and that's where WebGPU comes in.
|
||||
|
||||
## What is WebGPU?
|
||||
|
||||
WebGPU is a new browser API that gives JavaScript access to the system's GPU, not just for drawing graphics, but for running large-scale parallel computations like matrix operations and tensor math.
|
||||
|
||||
In our case, WebGPU lets the browser perform the heavy math required to generate text from an LLM.
|
||||
|
||||
Here's what WebGPU does for us:
|
||||
|
||||
- **Performance**: Runs faster than JavaScript or even WebAssembly for these workloads
|
||||
- **GPU-first**: Designed from the ground up for compute, not just rendering
|
||||
- **Accessibility**: Available across different browsers, though support varies by platform. As of June 2025:
|
||||
|
||||
- **Chrome/Edge**: Fully supported on Windows, Mac, and ChromeOS since version 113. On Linux, it requires enabling the `chrome://flags/#enable-unsafe-webgpu` flag
|
||||
- **Firefox**: Available in Nightly builds by default, with stable release tentatively planned for Firefox 141
|
||||
- **Safari**: Available in Safari Technology Preview, with support in iOS 18 and visionOS 2 betas via Feature Flags
|
||||
- **Android**: Chrome 121+ supports WebGPU on Android
|
||||
|
||||
For production applications, you should include proper WebGPU feature detection and provide fallbacks for unsupported browsers.
|
||||
|
||||
Together, WebLLM and WebGPU allow us to do something powerful: load a quantized language model directly in the browser and have real-time chat without any backend server.
|
||||
|
||||
With this understanding of WebLLM and WebGPU, we can now start building!
|
||||
|
||||
## Setting up the HTML for our AI chat app
|
||||
|
||||
Before we write any JavaScript, we need a user interface. This will be the visible part of our app, the dropdown for selecting models, the chat area, and the input box.
|
||||
|
||||
Here's the plan:
|
||||
|
||||
- We'll create a container for everything
|
||||
- Add a select box for choosing the model
|
||||
- Include a progress bar for when the model is loading
|
||||
- Display the chat history
|
||||
- Create a form with a text input and a button to submit prompts
|
||||
|
||||
To begin, create an `index.html` file and paste the following code inside it:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Browser LLM Chat Demo</title>
|
||||
<style>
|
||||
/* We'll add styling here. But later! */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<h1>Chat with LLM (In your browser)</h1>
|
||||
|
||||
<div class="controls">
|
||||
<select id="model-select">
|
||||
<option value="SmolLM2-360M-Instruct-q4f32_1-MLC">
|
||||
SmolLM2 360M (Very Small)
|
||||
</option>
|
||||
<option value="Phi-3.5-mini-instruct-q4f32_1-MLC">
|
||||
Phi 3.5 Mini (Medium)
|
||||
</option>
|
||||
<option value="Llama-3.1-8B-Instruct-q4f32_1-MLC">
|
||||
Llama 3.1 8B (Large)
|
||||
</option>
|
||||
</select>
|
||||
<button id="load-model">Load Model</button>
|
||||
</div>
|
||||
|
||||
<div class="chat-container">
|
||||
<div id="output">Select a model and click "Load Model" to begin</div>
|
||||
|
||||
<div
|
||||
id="progress-container"
|
||||
class="progress-container"
|
||||
style="display: none"
|
||||
>
|
||||
<div class="progress-bar">
|
||||
<div id="progress-fill" class="progress-fill"></div>
|
||||
</div>
|
||||
<div id="progress-text" class="progress-text">0%</div>
|
||||
</div>
|
||||
|
||||
<form id="chat-form" class="form-group">
|
||||
<input id="prompt" placeholder="Type your question..." disabled />
|
||||
<button type="submit" disabled>Send</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
In the HTML file, we've created a chat interface with controls for model selection and loading. The interface includes a chat output area, progress indicators, and an input form, which we need to let users interact with the AI model.
|
||||
|
||||
### Model selection
|
||||
|
||||
Notice that in the `div` with class `controls`, we have a `select` element for model selection and a `button` for loading the model. Here are the specifications for each model:
|
||||
|
||||
| Model | Parameters | Q4 file size | VRAM needed |
|
||||
| ------------ | ------------ | ------------ | ----------- |
|
||||
| SmolLM2-360M | 360 million | ~270 MB | ~380 MB |
|
||||
| Phi-3.5-mini | 3.8 billion | ~2.4 GB | ~3.7 GB |
|
||||
| Llama-3.1-8B | 8.03 billion | ~4.9 GB | ~5 GB |
|
||||
|
||||
When you're deciding which of these models to use in a browser environment with WebLLM, think first about what kind of work you want it to handle.
|
||||
|
||||
- **SmolLM2-360M** is the smallest by a wide margin, which means it loads quickly and puts the least strain on your device. If you're writing short notes, rewriting text, or making quick coding helpers that run in a browser, this might be all you need.
|
||||
|
||||
- **Phi-3.5-mini** brings more parameters and more capacity for reasoning, even though it still runs entirely in your browser. It's good for handling multi-step explanations, short document summarisation, or answering questions about moderately long prompts. If you're looking for a balance between size and capability, Phi-3.5-mini has a comfortable middle ground.
|
||||
|
||||
- **Llama-3.1-8B** is the largest of the three and carries more of the general knowledge and pattern recognition that bigger models can offer. It's more reliable if you're dealing with open-ended dialogue, creative writing, or complex coding tasks. But you'll need more memory.
|
||||
|
||||
Each of these models trades off size, memory use, and output quality in different ways. So choosing the right one depends on what your hardware can handle and what kind of prompts you plan to work with. All can run directly in modern browsers with WebGPU support.
|
||||
|
||||
There are more models available at the [WebLLM repository](https://github.com/mlc-ai/web-llm), ranging from smaller models for mobile devices to larger ones for more capable systems.
|
||||
|
||||
With the HTML in place, the next thing we'll do is work on the JavaScript implementation, then add some CSS to make it look nice. We're saving the CSS for the last step so we can focus on the core features.
|
||||
|
||||
## Using WebLLM and WebGPU to build the chatbot
|
||||
|
||||
Our JavaScript will do four main things:
|
||||
|
||||
1. Load the selected model into memory
|
||||
2. Track the loading progress and show feedback
|
||||
3. Enable the chat form once the model is ready
|
||||
4. Stream the response back as the assistant types it out
|
||||
|
||||
We'll build this piece by piece.
|
||||
|
||||
### Step 1: Import WebLLM
|
||||
|
||||
We need to bring in WebLLM so we can access the model engine.
|
||||
|
||||
```js
|
||||
import { CreateMLCEngine } from '<https://esm.run/@mlc-ai/web-llm@0.2.79>'
|
||||
```
|
||||
|
||||
This gives us the function that will initialize the model.
|
||||
|
||||
### Step 2: Get references to the DOM elements
|
||||
|
||||
Let's wire up the interface. We'll grab the elements we need so we can update them later.
|
||||
|
||||
```js
|
||||
const output = document.getElementById('output')
|
||||
const form = document.getElementById('chat-form')
|
||||
const promptInput = document.getElementById('prompt')
|
||||
const submitButton = document.querySelector('button[type="submit"]')
|
||||
const modelSelect = document.getElementById('model-select')
|
||||
const loadModelButton = document.getElementById('load-model')
|
||||
const progressContainer = document.getElementById('progress-container')
|
||||
const progressFill = document.getElementById('progress-fill')
|
||||
const progressText = document.getElementById('progress-text')
|
||||
```
|
||||
|
||||
We'll use these to display messages, show progress, and control the form state.
|
||||
|
||||
### Step 3: Track the model engine
|
||||
|
||||
We need a variable to hold the model once it's loaded.
|
||||
|
||||
```js
|
||||
let engine = null
|
||||
```
|
||||
|
||||
We'll update this later when the user loads a model.
|
||||
|
||||
### Step 4: Show progress
|
||||
|
||||
When downloading and initializing the model, we want to keep the user informed.
|
||||
|
||||
```js
|
||||
const updateProgress = (percent) => {
|
||||
progressContainer.style.display = 'block'
|
||||
progressFill.style.width = `${percent}%`
|
||||
progressText.textContent = `${percent}%`
|
||||
}
|
||||
```
|
||||
|
||||
This sets the visual width of the progress bar and updates the percentage text.
|
||||
|
||||
### Step 5: Load the model
|
||||
|
||||
Here's the key function. We call this when the user clicks the "Load Model" button.
|
||||
|
||||
```js
|
||||
const loadModel = async (modelId) => {
|
||||
try {
|
||||
output.textContent = 'Initializing...'
|
||||
promptInput.disabled = true
|
||||
submitButton.disabled = true
|
||||
loadModelButton.disabled = true
|
||||
progressContainer.style.display = 'none'
|
||||
```
|
||||
|
||||
We first disable the interface to prevent interference while loading.
|
||||
|
||||
Next, we make sure the browser supports WebGPU:
|
||||
|
||||
```js
|
||||
if (!navigator.gpu) {
|
||||
throw new Error('WebGPU not supported in this browser...')
|
||||
}
|
||||
```
|
||||
|
||||
This check is crucial because WebGPU availability varies significantly across browsers and platforms. The code will gracefully fail if WebGPU isn't available, allowing you to show appropriate fallback content to users.
|
||||
|
||||
Then we download and initialize the model:
|
||||
|
||||
```js
|
||||
engine = await CreateMLCEngine(modelId, {
|
||||
initProgressCallback: (progress) => {
|
||||
let percent =
|
||||
typeof progress === 'number'
|
||||
? Math.floor(progress * 100)
|
||||
: Math.floor(progress.progress * 100)
|
||||
|
||||
updateProgress(percent)
|
||||
output.textContent = `Loading model... ${percent}%`
|
||||
},
|
||||
useIndexedDBCache: true,
|
||||
})
|
||||
```
|
||||
|
||||
Once complete:
|
||||
|
||||
```js
|
||||
output.textContent = 'Model ready! Ask me something!'
|
||||
promptInput.disabled = false
|
||||
submitButton.disabled = false
|
||||
loadModelButton.disabled = false
|
||||
} catch (error) {
|
||||
loadModelButton.disabled = false
|
||||
output.innerHTML += `<div class="error">Failed to load model: ${error.message}</div>`
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now the model is ready to use.
|
||||
|
||||
### Step 6: Handle chat form submission
|
||||
|
||||
When the user types a question and presses enter, this block sends it to the model:
|
||||
|
||||
```js
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!engine) {
|
||||
output.innerHTML += `<div class="error">No model loaded...</div>`
|
||||
return
|
||||
}
|
||||
|
||||
const prompt = promptInput.value.trim()
|
||||
if (!prompt) return
|
||||
|
||||
output.textContent = `You: ${prompt}\\n\\nAssistant: `
|
||||
promptInput.value = ''
|
||||
promptInput.disabled = true
|
||||
submitButton.disabled = true
|
||||
```
|
||||
|
||||
Then we stream the assistant's response:
|
||||
|
||||
```js
|
||||
try {
|
||||
const stream = await engine.chat.completions.create({
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
stream: true,
|
||||
})
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const token = chunk.choices[0].delta.content || ''
|
||||
output.textContent += token
|
||||
output.scrollTop = output.scrollHeight
|
||||
}
|
||||
|
||||
promptInput.disabled = false
|
||||
submitButton.disabled = false
|
||||
promptInput.focus()
|
||||
} catch (error) {
|
||||
output.innerHTML += `<div class="error">Error during chat: ${error.message}</div>`
|
||||
promptInput.disabled = false
|
||||
submitButton.disabled = false
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Step 7: Trigger the model load on button click
|
||||
|
||||
Finally, we hook up the "Load Model" button:
|
||||
|
||||
```js
|
||||
loadModelButton.addEventListener('click', async () => {
|
||||
await loadModel(modelSelect.value)
|
||||
})
|
||||
```
|
||||
|
||||
## Adding CSS styling
|
||||
|
||||
Now that we have the JavaScript in place, let's add some CSS to give the app a clean look. In the `<style>` tag in the `<head>` section of the HTML file, add the following CSS:
|
||||
|
||||
```css
|
||||
body {
|
||||
font-family: Inter, system-ui, -apple-system, sans-serif;
|
||||
background-color: #f9fafb;
|
||||
color: #111827;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: 2rem;
|
||||
overflow: hidden;
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 1.5rem;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
#output {
|
||||
background: #f3f4f6;
|
||||
padding: 1.25rem;
|
||||
min-height: 220px;
|
||||
border-radius: 12px;
|
||||
white-space: pre-wrap;
|
||||
overflow-y: auto;
|
||||
max-height: 420px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select {
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
padding: 0.8rem 1rem;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #fd366e;
|
||||
color: white;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background-color: #e62e60;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #ffa5c0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #dc2626;
|
||||
background-color: #fee2e2;
|
||||
padding: 0.8rem;
|
||||
border-radius: 10px;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: #e5e7eb;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background-color: #fd366e;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
input {
|
||||
box-sizing: border-box;
|
||||
margin: 0rem;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: rgba(253, 54, 110, 0.5);
|
||||
box-shadow: 0 0 0 1px rgba(253, 54, 110, 0.1);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Next, open the `index.html` file in your browser and you should see something like this:
|
||||
|
||||

|
||||
|
||||
Try loading a model and chatting with the AI. To be sure that this all runs locally, you can also turn off your internet connection and see if the app still works after initial model download.
|
||||
|
||||
## Conclusion
|
||||
|
||||
We've built a local chatbot that runs entirely in the browser. You can now load a model, chat with it, and have a real-time conversation, all without a server.
|
||||
|
||||
This is the future of AI. Local, private, and fast. And what we've done is just the beginning. You can build on this foundation, add chat history, improve UX, support longer contexts, or experiment with your own compiled models.
|
||||
|
||||
You can also check out a more complex version of this app that includes chat history and a few other features [here](https://github.com/ebenezerdon/webllm-offline-ai).
|
||||
|
||||
The source code for the app we built in this guide is available on [GitHub](https://github.com/appwrite-community/simple-webllm-chat).
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Building a chat app with Appwrite and Google Gemini](https://appwrite.io/blog/post/build-a-chat-app-with-appwrite-and-gemini)
|
||||
- [Build a chatbot with GPT-4o and Appwrite Functions](https://appwrite.io/blog/post/personal-chatbot-gpt-4o)
|
||||
- [Building a full-stack app with Svelte and Appwrite](https://appwrite.io/blog/post/build-fullstack-svelte-appwrite)
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
layout: post
|
||||
title: Everyone can do DevRel (but should they?)
|
||||
description: Exploring the question if everyone should be rushing towards a job in Developer Relations?
|
||||
date: 2024-01-07
|
||||
cover: /images/blog/everyone-can-do-devrel-but-should-they/cover.png
|
||||
timeToRead: 5
|
||||
author: aditya-oberai
|
||||
category: devrel
|
||||
---
|
||||
|
||||
One thing that I can say, having spent five years in communities and over two years as a Developer Advocate at Appwrite, is that Developer Relations (or DevRel) isn’t just a job but a philosophy. DevRel is something we can do by helping developers in our communities, sharing knowledge through our content, or writing code and building projects. That being said, should everyone be rushing towards a job in Developer Relations? Recently, I have observed a bubble developing around this space. I want to take this blog as an opportunity to share my opinion on whether everyone should chase DevRel as a career.
|
||||
|
||||
Before deciding whether everyone should become a DevRel professional, let’s discuss what DevRel is.
|
||||
|
||||
# Understanding DevRel
|
||||
|
||||
> Developer Relations, or DevRel, is a domain that focuses on maintaining relationships with the folks building on an organization's technologies or products.
|
||||
|
||||
Essentially DevRel acts as the **bridge between the code and the community**. DevRel folks are often responsible for maintaining communication between organizations and developers to ensure a better information flow and feedback loop. Thus, both entities have a better experience and growth path.
|
||||
|
||||

|
||||
|
||||
DevRel teams came into inception because as we move towards a more technologically-enabled world, we see more people developing solutions to problems using programming and computer science and more companies catering to such folks. With more and more organizations catering their services to developers, it became apparent that these organizations need people who can **build** with their platforms, **communicate** with developers, **educate** newcomers, **grow** their audience, etc. DevRel aims to fit this gap precisely.
|
||||
|
||||
Now that we’ve discussed what DevRel is, let’s talk briefly about what skills and experience DevRel practitioners need to succeed.
|
||||
|
||||
# What experience should DevRel practitioners possess
|
||||
|
||||
Considering that DevRel can be viewed as an amalgam of **code**, **content**, and **community**, most roles in this space need you to be a jack of all three trades while being a master of (at least) one. As a Developer Advocate at Appwrite, I provide technical assistance and contribute to our product aside from creating content for our blog and social media, developing new community initiatives, and so on.
|
||||
|
||||
My experience has taught me that, while there is always space to learn, as a DevRel practitioner (for example, Developer Advocate), you should gain experience on all three fronts (whether through dedicated jobs or by alternate means) before rushing into this career, and here is why:
|
||||
|
||||
## Writing code
|
||||
|
||||
The time I spent learning how to build projects and working on real-world solutions (through self-defined problems or my internship) has substantially helped me in my DevRel journey. Understanding the pros and cons of the product I advocate for from a technical standpoint allows me to build a better sense of developer empathy for our community members. If I couldn’t build with our product, it would be impossible for me to relate to their problems and help solve them. Moreover, being able to code allows me to experience the Developer Experience of our product and offer feedback to our engineers, which enables better product development.
|
||||
|
||||
## Creating content
|
||||
|
||||
One key factor for any successful DevRel team is communicating with the brand and product’s audience; content enables that in the best way possible. It’ll allow you to advocate for your product more accurately and increase your likability and relatability, which also benefits your personal and organization’s brands. Therefore, being able to create and deliver content (written, audio, or video) is a skill that DevRel practitioners must develop.
|
||||
|
||||
## Building communities
|
||||
|
||||
My journey toward Developer Relations started by volunteering in communities. From leading a hackathon community (among others) to working as a Community Intern at a coding education company, I gained experience in planning new initiatives, managing their execution, collaborating with external stakeholders, building an audience, sustaining engagement, and maintaining brand growth. This experience gave me insights into how companies perceive communities and why they are necessary for user feedback and audience growth. Like it or not, being a DevRel professional, you are contributing to the business at the end of the day. Aside from learning how to work with larger groups of people, building communities helps to evaluate how you can impact the growth of an organization.
|
||||
|
||||
# Is DevRel just another fad?
|
||||
|
||||
While I firmly insist that DevRel isn’t a fad, currently, a bubble is beginning to exist. Far too many people are prematurely rushing toward this domain for three reasons:
|
||||
|
||||
- DevRel might seem like an **easier, more accessible way into tech than software development** (in all honesty, this isn’t true at all!)
|
||||
- DevRel provides people with a **platform where their voice seems to matter more**.
|
||||
- DevRel work can help **grow someone’s personal brand**.
|
||||
|
||||
Truthfully, anyone chasing a DevRel career in this manner often does not sustain here for long. This is because DevRel has so much beneath the tip of the iceberg. Aside from just speaking at stages and delivering pre-planned content or engaging with people in communities and social media, people in DevRel are often invested in various functions such as developer experience testing and feedback, sample application development, planning new community initiatives, technical and community thought leadership, quantifying the impact the community has on the product’s growth, and more depending on what their products need. Many of these aren’t always done publicly, so it is difficult to perceive them.
|
||||
|
||||
It is necessary to evaluate whether these are functions you want to be involved in. Pre-maturely deciding whether DevRel is the space for you can also cause you to **burn out** rather severely and impact your career direction!
|
||||
|
||||
# Taking this decision
|
||||
|
||||
Truthfully, the decision on whether you want to start a career in Developer Relations isn’t one you need to rush. I spent two years volunteering in communities and worked for five months as a Community Intern, two months as a User Research Intern, and two months as a Software Developer Intern (aside from other contractual obligations) before I made up my mind to apply for my first Developer Relations role. These experiences, when combined, allowed me to understand my skills and experiences, which allowed me to develop my aspirations in this space.
|
||||
|
||||
The truth is that code, content, and community are vast spaces of their own; you can very well fall in love with any of these spaces individually and pursue your career there, or at least give yourself a chance to fail and move forward. As long as you spend time understanding the most suitable space for you to work in, you’ll be absolutely fine.
|
||||
|
||||
# A little about Appwrite
|
||||
|
||||
[Appwrite](http://appwrite.io/) is an open-source Backend-as-a-Service (BaaS) platform that lets you add Authentication, Databases, Functions, and Storage to your product and build any application at any scale, own your data, and use your preferred coding languages and tools.
|
||||
|
||||
Learn more about Appwrite:
|
||||
|
||||
- [Appwrite Docs](https://appwrite.io/docs)
|
||||
- [Appwrite Discord](https://appwrite.io/discord)
|
||||
- [Appwrite GitHub](https://github.com/appwrite)
|
||||
- [Appwrite YouTube channel](https://youtube.com/@appwrite)
|
||||
@@ -9,6 +9,7 @@ author: veeresh-mulge
|
||||
callToAction: true
|
||||
unlisted: true
|
||||
category: tutorial
|
||||
call_to_action: true
|
||||
---
|
||||
|
||||
Nearly [70% of startups fail](https://www.embroker.com/blog/startup-statistics/) within 2-5 years of starting operations, and while many factors contribute to this, one key factor is developing a product that doesn't align with customer needs.
|
||||
@@ -67,6 +68,8 @@ Founders often fear that if users don't like the MVP, it will ruin their busines
|
||||
|
||||
Founders often build an MVP without considering future scalability, resulting in products that cannot scale as user demand grows. Even in the MVP phase, think about scalability. Build a flexible, scalable infrastructure that can handle future growth.
|
||||
|
||||
{% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%}
|
||||
|
||||
# Examples MVPs that turned into successful products
|
||||
|
||||
There's no right or wrong way to build an MVP. Many big companies kicked off with MVPs. Some got started with just a short demo video, while others with a lean MVP. However, in all cases, they began with very limited functionality that solved a real problem for a small group of early users.
|
||||
|
||||
@@ -121,6 +121,22 @@ Messaging allows you to implement communications in your application. You can us
|
||||
- Firebase offers the infrastructure to implement push notifications via Firebase Cloud Messaging (FCM). Appwrite doesn’t have its own infrastructure but contains a provider to implement FCM.
|
||||
- Firebase offers In-App Messaging to help you engage your app's active users through targeted, contextual messages inside the app. Appwrite does not offer this feature yet but aims to do so in the future.
|
||||
|
||||
## Hosting
|
||||
|
||||
Hosting refers to the service that allows developers to deploy and serve web applications to users over the internet. Appwrite offers native hosting as part of its all-in-one platform. That means you can develop, deploy, and scale your application all from a single platform. Firebase requires connecting separate services like Firestore and Cloud Functions.
|
||||
|
||||
*Similarities:*
|
||||
|
||||
- Both Appwrite and Firebase utilize global Content Delivery Networks (CDNs) to ensure low-latency access to hosted content worldwide.
|
||||
- Each platform automatically provisions SSL certificates, ensuring that all content is served securely over HTTPS..
|
||||
- Both services support popular web frameworks, enabling seamless deployment of applications built with technologies like React, Next.js, and Angular.
|
||||
|
||||
*Differences:*
|
||||
|
||||
- Appwrite Sites runs in containerized environments with full control over server-side rendering. Firebase Hosting is optimized for static content and uses Google’s managed infrastructure.
|
||||
- Appwrite is open-source and self-hostable. Firebase is fully managed by Google with no self-hosting support.
|
||||
- Appwrite is an all-in-one solution that covers everything from hosting and authentication to databases and cloud functions in one platform. Firebase also offers these services but manages them across separate tools, requiring more integration effort.
|
||||
|
||||
# Conclusion
|
||||
|
||||
While both Appwrite and Firebase are great Backend-as-a-Service offerings that support numerous SDKs and integrations, they differ in terms of capabilities and pricing. The choice between Appwrite and Firebase hinges on the specific needs of a project. Appwrite stands out with its open-source nature, self-hosting capabilities, pricing affordability, and emphasis on privacy. The community is very welcoming and is praised for it. Firebase's strength lies in its comprehensive ecosystem, Google support, and maturity.
|
||||
|
||||
@@ -9,6 +9,8 @@ author: veeresh-mulge
|
||||
callToAction: true
|
||||
unlisted: true
|
||||
category: tutorial
|
||||
call_to_action: true
|
||||
|
||||
---
|
||||
B2B enterprise SaaS is about to undergo a major shift. While SaaS revolutionized how software is built and distributed, allowing businesses to streamline operations with cloud-based tools, it had one major problem: it’s still built on a one-size-fits-all approach, with general-purpose solutions that require constant customization and integration. This led to complexity and inefficiency, as businesses needed multiple tools and human resources to make everything work.
|
||||
|
||||
@@ -56,6 +58,8 @@ By default, vertical AI agents are designed to address the unique needs of speci
|
||||
### Early-mover advantage
|
||||
From healthcare to finance to retail, the application of vertical AI agents is virtually limitless. As vertical AI agents are still relatively new, there’s a massive opportunity for early movers to establish themselves as leaders in their chosen niche.
|
||||
|
||||
{% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%}
|
||||
|
||||
# Conclusion
|
||||
|
||||
Vertical AI agents are set to revolutionize industries, offering companies greater efficiency and cost savings by automating workflows and replacing software and human labor. With advancements in AI technology, the right market conditions, and a shift in startup strategies, now is the perfect time to jump into this space.
|
||||
|
||||
@@ -9,6 +9,7 @@ author: veeresh-mulge
|
||||
callToAction: true
|
||||
unlisted: true
|
||||
category: tutorial
|
||||
call_to_action: true
|
||||
---
|
||||
|
||||
With AI advancing at a rapid pace, 2025 is shaping up to be the year of AI agents. We're moving beyond basic chatbots and entering a new era where AI can autonomously solve complex tasks. So, in the near future, fully autonomous AI agents can scope out a project and complete it with all the necessary tools they need and with no help from human partners.
|
||||
@@ -76,6 +77,8 @@ When building agents, start with simple tasks or single-action models and gradua
|
||||
|
||||
In the beginning, keep it simple, measure performance, and adjust the agent's behavior as needed before scaling up.
|
||||
|
||||
{% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%}
|
||||
|
||||
## Ensure Feedback Mechanisms
|
||||
|
||||
Incorporating feedback loops is essential for refining an agent's performance. In particular, coding agents can greatly benefit from feedback through tests that verify the correctness of their output.
|
||||
|
||||
159
src/routes/blog/post/understand-oauth2/+page.markdoc
Normal file
@@ -0,0 +1,159 @@
|
||||
---
|
||||
layout: post
|
||||
title: "Understanding OAuth2: The backbone of modern authorization"
|
||||
description: A quick guide to OAuth2, its flows, and when to use each one.
|
||||
date: 2025-06-12
|
||||
cover: /images/blog/understand-oauth2/cover.png
|
||||
timeToRead: 06
|
||||
author: laura-du-ry
|
||||
callToAction: true
|
||||
unlisted: true
|
||||
category: product
|
||||
---
|
||||
|
||||
In today’s interconnected app ecosystem, users expect seamless, secure access across services. OAuth2 has emerged as the industry standard for handling secure delegated access, making it a critical protocol for developers to understand.
|
||||
|
||||
This guide explains OAuth2, how it works, the different flows available, and when to use each one, helping you build secure, scalable authorization experiences.
|
||||
|
||||
# What is OAuth2?
|
||||
|
||||
OAuth2 is an open standard for authorization. It allows users to grant limited access to their resources on one service to another service without sharing credentials.
|
||||
|
||||
Rather than handing out a username and password, users authorize apps to act on their behalf using access tokens. OAuth2 ensures that:
|
||||
|
||||
- Apps never directly handle user credentials.
|
||||
- Users retain control over what permissions they grant.
|
||||
- Access can be easily revoked.
|
||||
|
||||
# Core components of OAuth2
|
||||
|
||||
Before diving into the flows, it's important to understand the key players:
|
||||
|
||||
- **Resource owner**: The user who authorizes access to their data.
|
||||
- **Client**: The application requesting access.
|
||||
- **Authorization server**: Issues access tokens after authenticating the user.
|
||||
- **Resource server**: Hosts the protected resources.
|
||||
|
||||
These components work together to ensure secure authorization across systems.
|
||||
|
||||
Refer to the OAuth2 [documentation](/docs/product/auth/oauth2) for complete technical details.
|
||||
|
||||
# How OAuth2 works: A simple flow
|
||||
|
||||
1. **Authorization request**: The client asks the resource owner for permission.
|
||||
2. **Authorization grant**: If the user consents, the server issues a grant (authorization code, token, etc.).
|
||||
3. **Token request**: The client exchanges the grant for an access token.
|
||||
4. **Resource access**: The client uses the token to access protected resources.
|
||||
|
||||
Tokens are typically short-lived and scoped, meaning they only allow the operations the user approved.
|
||||
|
||||
# Major OAuth2 flows
|
||||
|
||||
OAuth2 offers different "flows" to accommodate various scenarios. Here's a breakdown of the major ones:
|
||||
|
||||
## 1. Authorization code flow
|
||||
|
||||
**Best for**: Server-side applications
|
||||
|
||||
- User authenticates via browser.
|
||||
- Client receives an authorization code.
|
||||
- Server exchanges the code for an access token.
|
||||
|
||||
**Advantages**:
|
||||
|
||||
- Highly secure (authorization code exchanged server-side).
|
||||
- Supports refresh tokens.
|
||||
|
||||
**Typical use cases**:
|
||||
|
||||
- Web apps with secure backend servers.
|
||||
|
||||
{% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="Multiple OAuth providers" point2="Built-in security" point3="Custom roles and permissions" point4="Integrates with your favourite SDK" cta="Contact sales" url="https://appwrite.io/contact-us/enterprise" /%}
|
||||
|
||||
|
||||
## 2. Authorization code flow with PKCE (Proof Key for Code Exchange)
|
||||
|
||||
**Best for**: Mobile and SPA (Single Page Applications)
|
||||
|
||||
- Similar to Authorization Code Flow, but with an added security layer (PKCE).
|
||||
- Prevents interception attacks.
|
||||
|
||||
**Advantages**:
|
||||
|
||||
- Stronger protection for public clients.
|
||||
|
||||
**Typical use cases**:
|
||||
|
||||
- Mobile apps, SPAs.
|
||||
|
||||
## 3. Client credentials flow
|
||||
|
||||
**Best for**: Machine-to-machine (M2M) communication
|
||||
|
||||
- No user interaction.
|
||||
- Client authenticates itself to obtain an access token.
|
||||
|
||||
**Advantages**:
|
||||
|
||||
- Efficient for service-to-service communication.
|
||||
|
||||
**Typical use cases**:
|
||||
|
||||
- APIs accessed by backend services.
|
||||
|
||||
## 4. Implicit Flow (Legacy)
|
||||
|
||||
**Best for**: SPAs (historically)
|
||||
|
||||
- Tokens returned directly in browser URL.
|
||||
- Faster but less secure.
|
||||
|
||||
**Note**: Now largely replaced by Authorization Code Flow with PKCE due to security risks.
|
||||
|
||||
## 5. Device authorization flow
|
||||
|
||||
**Best for**: Devices without browsers/keyboards
|
||||
|
||||
- User authenticates on a separate device.
|
||||
- Device polls authorization server for approval.
|
||||
|
||||
**Typical use cases**:
|
||||
|
||||
- Smart TVs, IoT devices.
|
||||
|
||||
[Appwrite Auth](/products/auth) supports all major OAuth2 flows, making it easy to integrate secure authentication into any app
|
||||
|
||||
# OAuth2 Tokens: Access and refresh
|
||||
|
||||
OAuth2 commonly uses two types of tokens:
|
||||
|
||||
- **Access Token**: Grants access to protected resources.
|
||||
- **Refresh Token**: Used to obtain new access tokens without re-authenticating the user.
|
||||
|
||||
Tokens are often JWTs (JSON Web Tokens) containing claims about the user and the permissions granted.
|
||||
|
||||
# When to Use OAuth2
|
||||
|
||||
- **Third-party integrations**: Allowing users to connect external services securely.
|
||||
- **APIs**: Protecting APIs from unauthorized access.
|
||||
- **Mobile and web Apps**: Enabling secure login and data access without managing credentials.
|
||||
- **B2B applications**: Secure service-to-service communication.
|
||||
|
||||
# Common OAuth2 pitfalls
|
||||
|
||||
- **Over-scoped tokens**: Granting too many permissions.
|
||||
- **Insecure storage**: Storing tokens in insecure locations (e.g., localStorage without encryption).
|
||||
- **Ignoring token expiration**: Failing to handle token refresh flows.
|
||||
- **Misusing Implicit Flow**: Using legacy flows where better options (PKCE) are available.
|
||||
|
||||
# OAuth2: A key enabler of modern security
|
||||
|
||||
OAuth2 powers secure, flexible authorization across the modern internet. Understanding its core flows and best practices helps developers build safer, more user-friendly apps.
|
||||
|
||||
Choosing the proper OAuth2 flow based on your application's architecture and user needs is critical to balancing security, usability, and scalability.
|
||||
|
||||
Ready to explore OAuth2 more deeply? Check
|
||||
|
||||
- [Appwrite Authentication docs](/docs/products/auth)
|
||||
- [Overview of all the OAuth providers](/integrations#auth)
|
||||
- [Appwrite Authentication overview](/products/auth)
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
layout: post
|
||||
title: "Understanding IdP vs SP-Initiated SSO"
|
||||
description: A quick guide to IdP vs SP-initiated SSO and when to use each.
|
||||
date: 2025-06-16
|
||||
cover: /images/blog/understanding-idp-vs-sp-initiated-sso/cover.png
|
||||
timeToRead: 06
|
||||
author: laura-du-ry
|
||||
callToAction: true
|
||||
unlisted: true
|
||||
category: product
|
||||
---
|
||||
|
||||
Managing authentication across multiple applications is a growing challenge for developers, especially with users expecting more convenience and security. Single Sign-On (SSO) offers a practical solution to that problem, allowing users to access multiple services with one login. Although the experience is almost always seamless for users, developers have multiple options for implementing SSO in their applications.
|
||||
|
||||
This guide breaks down the differences between **Identity Provider (IdP)-initiated** and **Service Provider (SP)-initiated** SSO, their advantages and trade-offs, and how to choose the best fit for your setup.
|
||||
|
||||
# What is IdP-Initiated SSO?
|
||||
|
||||
First, a quick refresher: an **Identity Provider (IdP)** manages user identities, validating who a user is before granting access to different applications. Here’s a quick [overview](/docs/products/auth/identities) of how Appwrite handles identity and access.
|
||||
|
||||
In an IdP-initiated SSO flow, the user’s journey starts at the IdP itself:
|
||||
|
||||
# How it works
|
||||
|
||||
1. User logs in to the IdP.
|
||||
2. The IdP displays a dashboard of connected applications.
|
||||
3. The user selects a service to access.
|
||||
4. The IdP sends a secure authentication token (such as a SAML assertion) to the Service Provider (SP).
|
||||
5. The SP grants access based on the [token](/docs/products/auth/tokens).
|
||||
|
||||
# Advantages
|
||||
|
||||
- **Streamlined access**: Launch multiple services from a single dashboard.
|
||||
- **Reduced credential reuse**: Minimizes repeated logins, lowering the risk of compromised credentials.
|
||||
- **Centralized control**: Simplifies user monitoring and access management.
|
||||
|
||||
# Trade-offs
|
||||
|
||||
- **Extra navigation step**: Users must first visit the IdP portal.
|
||||
- **Single point of failure**: If the IdP is compromised, multiple services could be at risk.
|
||||
- **Integration challenges**: Some services may not fully support IdP-initiated workflows.
|
||||
|
||||
{% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="GDPR, HIPAA and SOC 2 compliant" point2="Built-in security" point3="Multi-factor authentication" point4="Integrates with your favourite SDK" cta="Contact sales" url="/contact-us/enterprise" /%}
|
||||
|
||||
# What is SP-Initiated SSO?
|
||||
|
||||
**Service Providers (SPs)** are the applications or services users want to access.
|
||||
|
||||
In SP-initiated SSO, the process begins when a user attempts to log into an application directly:
|
||||
|
||||
# How it works
|
||||
|
||||
1. User tries to access the service.
|
||||
2. The service detects no active session and redirects the user to the IdP.
|
||||
3. The user authenticates at the IdP.
|
||||
4. The IdP sends an authentication token back to the service.
|
||||
5. The service grants access.
|
||||
|
||||
# Advantages
|
||||
|
||||
- **Direct access**: Users can go straight to the service they want.
|
||||
- **Seamless integration**: Fits naturally into user-driven workflows.
|
||||
- **Flexibility**: Useful for both internal and external users.
|
||||
|
||||
# Trade-offs
|
||||
|
||||
- **Redirect dependency**: Requires smooth coordination between service and IdP.
|
||||
- **Increased setup complexity**: Proper configuration is critical to avoid login issues.
|
||||
|
||||
# IdP- vs SP-Initiated SSO: Quick Comparison
|
||||
|
||||
| Feature | IdP-Initiated SSO | SP-Initiated SSO |
|
||||
| --- | --- | --- |
|
||||
| **Starting Point** | Identity Provider portal | Service Provider login page |
|
||||
| **User Flow** | Login at IdP, then select services | Attempt service access, then authenticate via IdP |
|
||||
| **User Experience** | Best for environments with multiple services | Best for quick, direct service access |
|
||||
| **Security Considerations** | Central control but single point of vulnerability | Stronger per-service session security |
|
||||
| **Typical Use Cases** | Corporate portals, education hubs | SaaS apps, customer-facing platforms |
|
||||
|
||||
# When to choose IdP-Initiated SSO
|
||||
|
||||
- **Organizations with many internal services**: Ideal for centralized portals.
|
||||
- **Formal environments**: Where users are accustomed to navigating through a unified dashboard.
|
||||
- **Legacy system compatibility**: Easier integration with older systems.
|
||||
|
||||
# When to Choose SP-Initiated SSO
|
||||
|
||||
- **User-first services**: Where users need to quickly access a single app.
|
||||
- **B2B and B2C platforms**: Especially when users might come in via bookmarks, emails, or direct links.
|
||||
- **Dynamic environments**: Where new apps are frequently added or removed.
|
||||
|
||||
Pro tip: SP-initiated flows are often complemented by [adaptive MFA](/docs/products/auth/mfa) to enhance security without compromising the user experience.
|
||||
|
||||
# When to use both approaches
|
||||
|
||||
Many organizations implement both IdP- and SP-initiated SSO to serve different user needs:
|
||||
|
||||
- **Employee and partner ecosystems**: Employees might use IdP dashboards while partners or customers prefer direct access.
|
||||
- **Hybrid cloud setups**: Supporting a mix of legacy and modern applications.
|
||||
- **Adaptive security strategies**: Choosing the flow based on device, location, or user profile.
|
||||
|
||||
Choosing the right SSO initiation method,or blending both, can dramatically impact [security](/docs/products/auth/security), user satisfaction, and scalability. Evaluate your platform's user behavior, security posture, and integration needs to pick the best approach for your environment.
|
||||
|
||||
# Futher reading
|
||||
|
||||
- [Appwrite Authentication docs](/docs/products/auth)
|
||||
- [Developer's guide to user authentication](/blog/post/guide-to-user-authentication)
|
||||
- [Appwrite Authentication overview](/products/auth)
|
||||
|
||||
@@ -153,6 +153,11 @@
|
||||
label: 'Abuse',
|
||||
href: '/docs/advanced/platform/abuse'
|
||||
},
|
||||
{
|
||||
new: isNewUntil('31 July 2025'),
|
||||
label: 'Support SLA',
|
||||
href: '/docs/advanced/platform/support-sla'
|
||||
},
|
||||
{
|
||||
new: isNewUntil('31 July 2025'),
|
||||
label: 'Uptime SLA',
|
||||
|
||||
54
src/routes/docs/advanced/platform/support-sla/+page.markdoc
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
layout: article
|
||||
title: Support SLA
|
||||
description: Learn about Appwrite's support service level agreement (SLA) including response times, severity levels, and support commitments for different subscription tiers.
|
||||
---
|
||||
|
||||
This Support Service Level Agreement ("SLA") describes the support services provided by APPWRITE ("we," "us," or "our") to users of our products and services ("you" or "user"). By using our services, you agree to the terms of this SLA.
|
||||
|
||||
## Scope
|
||||
|
||||
This SLA outlines our commitments for providing support services via email, including response and resolution processes based on issue severity. The specific response times depend on the support tier associated with your subscription: **Pro**, **Scale**, or **Enterprise**.
|
||||
|
||||
## Severity levels
|
||||
|
||||
Support issues are categorized into the following severity levels:
|
||||
|
||||
- **Critical**: System is down or a critical component is non-functional, causing a complete stoppage of work or significant business impact.
|
||||
- **High**: Major functionality is impaired, but a workaround is available, or a critical component is significantly degraded.
|
||||
- **Medium**: Minor functionality is impaired without significant business impact.
|
||||
- **Low**: Issue has minor impact on business operations; workaround is not necessary.
|
||||
- **Question**: Requests for information, general guidance, or feature requests.
|
||||
|
||||
## Response time targets
|
||||
|
||||
| Severity | Pro | Scale | Enterprise |
|
||||
| --- | --- | --- | --- |
|
||||
| Critical | Unsupported | 1 hour (24/7/365) | 15 minutes (24/7/365) |
|
||||
| High | Unsupported | 4 hours (24/7/365) | 1 hour (24/7/365) |
|
||||
| Medium | 2 business days | 1 business day | 12 hours (24/7/365) |
|
||||
| Low | 3 business days | 2 business days | 24 hours (24/7/365) |
|
||||
| Question | 4 business days | 3 business days | 1 business day |
|
||||
|
||||
## Business hours and days
|
||||
|
||||
Our standard business hours are from **9:00 AM to 5:00 PM Pacific Time**, Monday through Friday, excluding public holidays. Enterprise and Scale customers receive extended support 24/7/365.
|
||||
|
||||
## User responsibilities
|
||||
|
||||
To ensure effective support, users are expected to:
|
||||
|
||||
- Provide detailed information about each issue, including screenshots, error messages, logs, and steps to reproduce the problem.
|
||||
- Ensure relevant personnel are available to assist in diagnosing and resolving issues.
|
||||
- Implement reasonable recommendations provided by our support team.
|
||||
|
||||
## Limitations and exclusions
|
||||
|
||||
- This SLA applies only to support requests submitted via the Appwrite Console.
|
||||
- SLA obligations may be affected by factors outside our reasonable control, including but not limited to force majeure events, third-party dependencies, or actions taken by the user.
|
||||
|
||||
## Modifications
|
||||
|
||||
We reserve the right to modify this SLA at any time. Changes become effective upon posting to our website. Your continued use of our services after changes indicates your acceptance of the updated SLA.
|
||||
|
||||
For questions or concerns about this SLA, please contact us at our [contact page](https://appwrite.io/contact-us).
|
||||
@@ -109,8 +109,8 @@ If running in production, it might be easier to use a 3rd party SMTP server as i
|
||||
| `_APP_STORAGE_S3_ACCESS_KEY` | **version >= 0.13.0** AWS S3 storage access key. Required when the storage adapter is set to S3. You can get your access key from your AWS console. |
|
||||
| `_APP_STORAGE_S3_SECRET` | **version >= 0.13.0** AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console. |
|
||||
| `_APP_STORAGE_S3_REGION` | **version >= 0.13.0** AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console. |
|
||||
| `_APP_STORAGE_S3_BUCKET` | **version >= 0.13.0** AWS S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your AWS console. |
|
||||
| `_APP_STORAGE_S3_ENDPOINT` | **version >= 1.7.0** Override the S3 endpoint to use an S3-compatible provider. This should just be the host (without 'https://'). |
|
||||
| `_APP_STORAGE_S3_BUCKET` | **version >= 0.13.0** AWS S3 storage bucket. Required when storage adapter is set to S3 and using path-style requests (where the bucket is in the path). You can create buckets in your AWS console. If using virtual-hosted-style paths where the bucket is in the endpoint URL, this should be empty. |
|
||||
| `_APP_STORAGE_S3_ENDPOINT` | **version >= 1.7.0** Override the S3 endpoint to use an S3-compatible provider. This should just be the host (without 'https://'). If using virtual-hosted-style paths where the bucket is included in the endpoint (e.g., `bucket-name.s3.amazonaws.com`), `_APP_STORAGE_S3_BUCKET` should be empty. For path-style requests, the endpoint should not include the bucket name and `_APP_STORAGE_S3_BUCKET` should be set. |
|
||||
| `_APP_STORAGE_DO_SPACES_ACCESS_KEY` | **version >= 0.13.0** DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console. |
|
||||
| `_APP_STORAGE_DO_SPACES_SECRET` | **version >= 0.13.0** DigitalOcean spaces secret key. Required when the storage adapter is set to DOSpaces. You can get your secret key from your DigitalOcean console. |
|
||||
| `_APP_STORAGE_DO_SPACES_REGION` | **version >= 0.13.0** DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console. |
|
||||
|
||||
@@ -97,6 +97,10 @@
|
||||
label: 'Multi-factor authentication',
|
||||
href: '/docs/products/auth/mfa'
|
||||
},
|
||||
{
|
||||
label: 'Auth status check',
|
||||
href: '/docs/products/auth/checking-auth-status'
|
||||
},
|
||||
{
|
||||
label: 'User verification',
|
||||
href: '/docs/products/auth/verify-user'
|
||||
|
||||
146
src/routes/docs/products/auth/checking-auth-status/+page.markdoc
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
layout: article
|
||||
title: Checking auth status
|
||||
description: Learn how to check a user's authentication status in your Appwrite application and handle authentication flow appropriately.
|
||||
---
|
||||
|
||||
One of the first things your application needs to do when starting up is to check if the user is authenticated. This is an important step in creating a great user experience, as it determines whether to show login screens or protected content.
|
||||
|
||||
# Check auth with `account.get()`
|
||||
|
||||
The recommended approach for checking authentication status is to use the `account.get()` method when your application starts:
|
||||
|
||||
{% multicode %}
|
||||
```client-web
|
||||
import { Client, Account } from "appwrite";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
|
||||
.setProject('<PROJECT_ID>');
|
||||
|
||||
const account = new Account(client);
|
||||
|
||||
// Check if user is logged in
|
||||
async function checkAuthStatus() {
|
||||
try {
|
||||
// If successful, user is authenticated
|
||||
const user = await account.get();
|
||||
console.log("User is authenticated:", user);
|
||||
// Proceed with your authenticated app flow
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error("User is not authenticated:", error);
|
||||
// Redirect to login page or show login UI
|
||||
// window.location.href = '/login';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function when your app initializes
|
||||
checkAuthStatus();
|
||||
```
|
||||
```client-flutter
|
||||
import 'package:appwrite/appwrite.dart';
|
||||
|
||||
void checkAuthStatus() async {
|
||||
final client = Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
|
||||
.setProject('<PROJECT_ID>');
|
||||
|
||||
final account = Account(client);
|
||||
|
||||
try {
|
||||
// If successful, user is authenticated
|
||||
final user = await account.get();
|
||||
print('User is authenticated: ${user.name}');
|
||||
// Proceed with your authenticated app flow
|
||||
} catch (e) {
|
||||
print('User is not authenticated: $e');
|
||||
// Redirect to login page or show login UI
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function when your app initializes
|
||||
```
|
||||
```client-android-kotlin
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
import io.appwrite.exceptions.AppwriteException
|
||||
|
||||
class AuthManager {
|
||||
private val client = Client(context)
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
|
||||
.setProject("<PROJECT_ID>")
|
||||
|
||||
private val account = Account(client)
|
||||
|
||||
suspend fun checkAuthStatus(): Boolean {
|
||||
return try {
|
||||
val user = account.get()
|
||||
Log.d("Auth", "User is authenticated: ${user.name}")
|
||||
// Proceed with your authenticated app flow
|
||||
true
|
||||
} catch (e: AppwriteException) {
|
||||
Log.e("Auth", "User is not authenticated: ${e.message}")
|
||||
// Redirect to login page or show login UI
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function when your app initializes
|
||||
```
|
||||
```client-apple
|
||||
import Appwrite
|
||||
|
||||
func checkAuthStatus() {
|
||||
let client = Client()
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
|
||||
.setProject("<PROJECT_ID>")
|
||||
|
||||
let account = Account(client)
|
||||
|
||||
Task {
|
||||
do {
|
||||
// If successful, user is authenticated
|
||||
let user = try await account.get()
|
||||
print("User is authenticated: \(user.name)")
|
||||
// Proceed with your authenticated app flow
|
||||
} catch {
|
||||
print("User is not authenticated: \(error)")
|
||||
// Redirect to login page or show login UI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function when your app initializes
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
# Missing scope error
|
||||
|
||||
When a user is not authenticated and you call `account.get()`, you might see an error message like:
|
||||
|
||||
```
|
||||
User (role: guests) missing scope (account)
|
||||
```
|
||||
|
||||
This error is telling you that:
|
||||
1. The current user has the role of "guest" (unauthenticated visitor)
|
||||
2. This guest user does not have the required permission scope to access account information
|
||||
3. This is the expected behavior when a user is not logged in
|
||||
|
||||
{% info title="Authentication flow" %}
|
||||
In a typical application flow:
|
||||
|
||||
1. Call `account.get()` when your app starts
|
||||
2. If successful → User is authenticated → Show the main app UI
|
||||
3. If error → User is not authenticated → Redirect to login screen
|
||||
{% /info %}
|
||||
|
||||
# Best practices
|
||||
|
||||
- Call `account.get()` early in your application lifecycle
|
||||
- Handle both authenticated and unauthenticated states gracefully
|
||||
- Show appropriate loading states while checking authentication
|
||||
- Implement proper error handling to avoid showing error messages to users
|
||||
@@ -72,6 +72,17 @@ $adminClient = (new Client())
|
||||
->setKey('<YOUR_API_KEY>'); // Your secret API key
|
||||
|
||||
|
||||
```
|
||||
```python
|
||||
from appwrite.client import Client
|
||||
|
||||
admin_client = (Client()
|
||||
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint \
|
||||
.set_project('<PROJECT_ID>') # Your project ID
|
||||
.set_key('<YOUR_API_KEY>') # Your secret API key
|
||||
)
|
||||
|
||||
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
@@ -105,6 +116,22 @@ if ($session) {
|
||||
$sessionClient->setSession($session);
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
from flask import request
|
||||
from appwrite.client import Client
|
||||
|
||||
session_client = (Client()
|
||||
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
|
||||
.set_project('<PROJECT_ID>') # Your project ID
|
||||
)
|
||||
|
||||
# Get the session cookie from the request
|
||||
session = request.cookies.get('session')
|
||||
if session:
|
||||
session_client.set_session(session)
|
||||
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
# Creating email/password sessions {% #creating-sessions %}
|
||||
@@ -178,6 +205,39 @@ try {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
```
|
||||
```python
|
||||
from flask import Flask, request, jsonify, make_response
|
||||
|
||||
# Initialize admin client here
|
||||
# ...
|
||||
|
||||
@app.post('/login')
|
||||
def login():
|
||||
body = request.json
|
||||
# Get email and password from request
|
||||
email = body['email']
|
||||
password = body['password']
|
||||
|
||||
try:
|
||||
account = Account(admin_client)
|
||||
|
||||
# Create the session using the Appwrite client
|
||||
session = account.create_email_password_session(email, password)
|
||||
resp = make_response(jsonify({'success': True}))
|
||||
|
||||
# Set the session cookie
|
||||
resp.set_cookie('session',
|
||||
session['secret'],
|
||||
httponly=True,
|
||||
secure=True,
|
||||
samesite='Strict',
|
||||
expires=session['expire'],
|
||||
path='/'
|
||||
)
|
||||
return resp
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 400
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
We also recommend using the `httpOnly`, `secure`, and `sameSite` cookie options to ensure that the cookie is only sent over HTTPS,
|
||||
@@ -242,6 +302,30 @@ try {
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
```
|
||||
```python
|
||||
# Initialize the session client here
|
||||
|
||||
@app.get('/user')
|
||||
def get_user():
|
||||
# First, read the session cookie from the request
|
||||
session = request.cookies.get('session')
|
||||
|
||||
# If the session cookie is not present, return an error
|
||||
if not session:
|
||||
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
|
||||
|
||||
# pass the session cookie to the Appwrite client
|
||||
session_client.set_session(session)
|
||||
account = Account(session_client)
|
||||
|
||||
# Now, you can make authenticated requests to the Appwrite API
|
||||
try:
|
||||
user = account.get()
|
||||
return jsonify({'success': True, 'user': user})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 400
|
||||
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
@@ -319,6 +403,19 @@ $account = new Account($client);
|
||||
|
||||
$result = $account->createAnonymousSession();
|
||||
```
|
||||
```python
|
||||
from appwrite.client import Client
|
||||
from appwrite.services.account import Account
|
||||
|
||||
client = (Client()
|
||||
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
|
||||
.set_project('<PROJECT_ID>') # Your project ID
|
||||
)
|
||||
|
||||
account = Account(client)
|
||||
|
||||
result = account.create_anonymous_session()
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
# Forwarding user agent {% #forwarding-user-agent %}
|
||||
@@ -333,6 +430,9 @@ client.setForwardedUserAgent(req.headers['user-agent']);
|
||||
<?php
|
||||
$client->setForwardedUserAgent($_SERVER['HTTP_USER_AGENT']);
|
||||
```
|
||||
```python
|
||||
client.set_forwarded_user_agent(request.headers.get('user-agent'))
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
# OAuth2 {% #oauth2 %}
|
||||
@@ -383,6 +483,29 @@ $redirectUrl = $account->createOAuth2Token(
|
||||
|
||||
header('Location' . $redirectUrl);
|
||||
```
|
||||
```python
|
||||
from appwrite.client import Client
|
||||
from appwrite.services.account import Account, OAuthProvider
|
||||
from flask import Flask, request ,redirect, make_response, jsonify
|
||||
|
||||
admin_client = (Client()
|
||||
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
|
||||
.set_project('<PROJECT_ID>')
|
||||
.set_key('<API_KEY>')
|
||||
)
|
||||
|
||||
@app.get('/oauth')
|
||||
def oauth():
|
||||
account = Account(admin_client)
|
||||
|
||||
redirect_url = account.create_o_auth2_token(
|
||||
OAuthProvider.Github, # Provider
|
||||
'https://example.com/oauth/success', # Success URL
|
||||
'https://example.com/oauth/failure', # Failure URL
|
||||
)
|
||||
|
||||
return redirect(redirect_url)
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
Next, create a success callback endpoint that receives the `userId` and `secret` URL parameters, and then calls `createSession` on the server side. This endpoint returns a session object, which you can store in a cookie.
|
||||
@@ -448,6 +571,38 @@ try {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
```
|
||||
```python
|
||||
@app.get('/oauth/success')
|
||||
def oauth_success():
|
||||
account = Account(admin_client)
|
||||
|
||||
# Get the userId and secret from the URL parameters
|
||||
user_id = request.args.get('userId')
|
||||
secret = request.args.get('secret')
|
||||
|
||||
try:
|
||||
# Create the session using the Appwrite client
|
||||
session = account.create_session(user_id, secret)
|
||||
|
||||
# Set the session cookie
|
||||
res = make_response(jsonify({'success': True}))
|
||||
|
||||
# Set session cookie
|
||||
res.set_cookie(
|
||||
'session',
|
||||
session['secret'],
|
||||
httponly=True,
|
||||
secure=True,
|
||||
samesite='Strict',
|
||||
max_age=session['expire'],
|
||||
path='/'
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 400
|
||||
```
|
||||
{% /multicode %}
|
||||
|
||||
Now the cookie is set, it will be passed to the server with subsequent requests, and you can use it to make authenticated requests to the Appwrite API on behalf of the end-user.
|
||||
|
||||
@@ -3,21 +3,25 @@
|
||||
import Docs from '$lib/layouts/Docs.svelte';
|
||||
import Sidebar, { type NavParent, type NavTree } from '$lib/layouts/Sidebar.svelte';
|
||||
import { preferredPlatform, preferredVersion } from '$lib/utils/references';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
$: expandable = !!page.url.pathname.match(
|
||||
/\/docs\/references\/.*?\/(client|server).*?\/.*?\/?/
|
||||
const { children }: { children: Snippet } = $props();
|
||||
|
||||
const expandable = $derived(
|
||||
!!page.url.pathname.match(/\/docs\/references\/.*?\/(client|server).*?\/.*?\/?/)
|
||||
);
|
||||
|
||||
$: platform = $preferredPlatform ?? page.params?.platform ?? 'client-web';
|
||||
const platform = $derived($preferredPlatform ?? page.params?.platform ?? 'client-web');
|
||||
|
||||
/* correct platform prefix for references page */
|
||||
$: resolvedPlatformPrefix = /^server-|^client-/.test(platform)
|
||||
? platform
|
||||
: `server-${platform}`;
|
||||
const resolvedPlatformPrefix = $derived(
|
||||
/^server-|^client-/.test(platform) ? platform : `server-${platform}`
|
||||
);
|
||||
|
||||
$: prefix = `/docs/references/${$preferredVersion ?? page.params?.version ?? 'cloud'}/${resolvedPlatformPrefix}`;
|
||||
const prefix = $derived(
|
||||
`/docs/references/${$preferredVersion ?? page.params?.version ?? 'cloud'}/${resolvedPlatformPrefix}`
|
||||
);
|
||||
|
||||
$: navigation = [
|
||||
const navigation: NavTree = $derived([
|
||||
{
|
||||
label: 'Getting started',
|
||||
items: [
|
||||
@@ -93,22 +97,7 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
// {
|
||||
// label: 'Debugging',
|
||||
// items: [
|
||||
// {
|
||||
// icon: 'icon-document-search',
|
||||
// label: 'Response codes',
|
||||
// href: '/docs/advanced/platform/response-codes'
|
||||
// },
|
||||
// {
|
||||
// icon: 'icon-document-report',
|
||||
// label: 'Rate-limits',
|
||||
// href: '/docs/advanced/platform/rate-limits'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
] as NavTree;
|
||||
]);
|
||||
|
||||
const parent: NavParent = {
|
||||
href: '/docs',
|
||||
@@ -118,5 +107,5 @@
|
||||
|
||||
<Docs variant={expandable ? 'expanded' : 'two-side-navs'} isReferences={expandable}>
|
||||
<Sidebar {navigation} {expandable} {parent} />
|
||||
<slot />
|
||||
{@render children()}
|
||||
</Docs>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<span class="text-micro text-primary uppercase">
|
||||
{response.code}
|
||||
</span>
|
||||
<span class="text-caption">application/json</span>
|
||||
<span class="text-caption">{response.contentType ?? 'no content'}</span>
|
||||
</header>
|
||||
{#if response.models.length > 0}
|
||||
<ul class="text-sub-body mt-4">
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
import Request from './(components)/Request.svelte';
|
||||
import Response from './(components)/Response.svelte';
|
||||
import RateLimits from './(components)/RateLimits.svelte';
|
||||
import type { SDKMethod } from '$lib/utils/specs';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -34,7 +35,6 @@
|
||||
const headings = getContext<LayoutContext>('headings');
|
||||
|
||||
let selected: string | undefined = $state(undefined);
|
||||
let selectedMenuItem: HTMLElement;
|
||||
|
||||
headings.subscribe((n) => {
|
||||
const noVisible = Object.values(n).every((n) => !n.visible);
|
||||
@@ -61,7 +61,9 @@
|
||||
correctPlatform = platform.replaceAll(`server-`, ``) as Platform;
|
||||
}
|
||||
|
||||
preferredPlatform.set(correctPlatform as Platform);
|
||||
if ($preferredPlatform === correctPlatform) return;
|
||||
|
||||
preferredPlatform.set(correctPlatform);
|
||||
|
||||
goto(`/docs/references/${version}/${platform}/${service}`, {
|
||||
noScroll: true
|
||||
@@ -71,6 +73,8 @@
|
||||
function selectVersion(event: CustomEvent<unknown>) {
|
||||
const { platform, service } = page.params;
|
||||
const version = event.detail as Version;
|
||||
if ($preferredVersion === version) return;
|
||||
|
||||
preferredVersion.set(version);
|
||||
goto(`/docs/references/${version}/${platform}/${service}`, {
|
||||
noScroll: true
|
||||
@@ -110,8 +114,7 @@
|
||||
: `server-${$preferredPlatform}`;
|
||||
|
||||
goto(`/docs/references/${$preferredVersion}/${platformMode}/${page.params.service}`, {
|
||||
noScroll: true,
|
||||
replaceState: false
|
||||
noScroll: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -145,7 +148,7 @@
|
||||
/**
|
||||
* Sorts methods by their operation order and title
|
||||
*/
|
||||
function sortMethods(methods: any[]) {
|
||||
function sortMethods(methods: SDKMethod[]) {
|
||||
return methods.sort((a, b) => {
|
||||
const orderA = getOperationOrder(a.title);
|
||||
const orderB = getOperationOrder(b.title);
|
||||
@@ -156,11 +159,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups methods by their group attribute, null group goes to '' for ordering
|
||||
*/
|
||||
function groupMethodsByGroup(methods: any[]) {
|
||||
return methods.reduce((acc, method) => {
|
||||
function groupMethodsByGroup(methods: SDKMethod[]) {
|
||||
return methods.reduce<Record<string, SDKMethod[]>>((acc, method) => {
|
||||
const groupKey = method.group || '';
|
||||
if (!acc[groupKey]) {
|
||||
acc[groupKey] = [];
|
||||
@@ -170,20 +170,6 @@
|
||||
}, {});
|
||||
}
|
||||
|
||||
function bindSelectedRef(node: HTMLElement, isSelected: boolean) {
|
||||
if (isSelected) {
|
||||
selectedMenuItem = node;
|
||||
}
|
||||
|
||||
return {
|
||||
update(newIsSelected: boolean) {
|
||||
if (newIsSelected) {
|
||||
selectedMenuItem = node;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let platformBindingForSelect = $derived(page.params.platform as Platform);
|
||||
let platform = $derived(/**$preferredPlatform ?? */ page.params.platform as Platform);
|
||||
let platformType = $derived(platform.startsWith('client-') ? 'CLIENT' : 'SERVER');
|
||||
@@ -400,7 +386,6 @@
|
||||
href={`#${method.id}`}
|
||||
class="web-references-menu-link text-caption"
|
||||
class:is-selected={method.id === selected}
|
||||
use:bindSelectedRef={method.id === selected}
|
||||
>
|
||||
{method.title}
|
||||
</a>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<Td>{property.name}</Td>
|
||||
<Td>{property.type}</Td>
|
||||
<Td>
|
||||
{property.description}
|
||||
{@html parse(property.description)}
|
||||
{#if property.relatedModels}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
Can be one of: {@html parse(property.relatedModels)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { base } from '$app/paths';
|
||||
import { groupBy } from 'remeda';
|
||||
import type { IntegrationCategory } from '$lib/constants';
|
||||
import { integrationCategoryDescriptions as categoryDescriptions } from '$lib/constants';
|
||||
import type { SearchableCategory } from '$lib/constants';
|
||||
import { partnerCategoryDescriptions as categoryDescriptions } from '$lib/constants';
|
||||
|
||||
export type Integration = {
|
||||
title: string;
|
||||
@@ -26,7 +26,7 @@ export const load = () => {
|
||||
eager: true
|
||||
});
|
||||
|
||||
const categories: IntegrationCategory[] = [];
|
||||
const categories: SearchableCategory[] = [];
|
||||
const platforms: string[] = [];
|
||||
|
||||
const integrations = Object.entries(integrationsGlob).map(([filepath, integrationList]) => {
|
||||
@@ -40,7 +40,7 @@ export const load = () => {
|
||||
frontmatter.platform.map((platform) => platforms.push(platform));
|
||||
categories.push(
|
||||
categoryDescriptions.find((i) => i.slug === frontmatter.category) ??
|
||||
({} as IntegrationCategory)
|
||||
({} as SearchableCategory)
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -50,10 +50,22 @@ Visit the `.env` file created for your Appwrite instance and update the followin
|
||||
|
||||
```bash
|
||||
_APP_STORAGE_DEVICE=s3
|
||||
_APP_STORAGE_S3_BUCKET=[BUCKET_NAME]
|
||||
_APP_STORAGE_S3_REGION=[AWS_REGION]
|
||||
_APP_STORAGE_S3_ACCESS_KEY=[ACCESS_KEY_ID]
|
||||
_APP_STORAGE_S3_SECRET=[SECRET_ACCESS_KEY]
|
||||
_APP_STORAGE_S3_BUCKET=<BUCKET_NAME>
|
||||
_APP_STORAGE_S3_REGION=<AWS_REGION>
|
||||
_APP_STORAGE_S3_ACCESS_KEY=<ACCESS_KEY_ID>
|
||||
_APP_STORAGE_S3_SECRET=<SECRET_ACCESS_KEY>
|
||||
```
|
||||
|
||||
Starting from version 1.7.0, you can also use S3-compatible providers by configuring the endpoint:
|
||||
|
||||
```bash
|
||||
# For path-style requests (bucket in the path)
|
||||
_APP_STORAGE_S3_ENDPOINT=<PROVIDER_ENDPOINT> # e.g., s3.amazonaws.com (without https://)
|
||||
_APP_STORAGE_S3_BUCKET=<BUCKET_NAME> # bucket name required here
|
||||
|
||||
# OR for virtual-hosted-style paths (bucket in the endpoint)
|
||||
_APP_STORAGE_S3_ENDPOINT=<BUCKET_NAME>.<PROVIDER_ENDPOINT> # e.g., bucket-name.s3.amazonaws.com
|
||||
_APP_STORAGE_S3_BUCKET= # leave this empty when bucket is in the endpoint
|
||||
```
|
||||
|
||||
After that, run the following Docker Compose commands in your terminal to restart your Appwrite containers and verify if the changes have been successfully applied:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{
|
||||
title: 'Training',
|
||||
description:
|
||||
'We provide in depth training and workshops to help you master Appwrite for your clients.',
|
||||
'We provide in-depth training and workshops to help you master Appwrite for your clients.',
|
||||
icon: Training
|
||||
},
|
||||
{
|
||||
@@ -36,9 +36,9 @@
|
||||
icon: EarlyAccess
|
||||
},
|
||||
{
|
||||
title: 'Revenue share',
|
||||
title: 'Innovation',
|
||||
description:
|
||||
'For each client you sell Appwrite to, you will receive a part of the revenue for a whole year.',
|
||||
"Empower your team and elevate your customers' experiences with the newest technology.",
|
||||
icon: Revenue
|
||||
},
|
||||
{
|
||||
@@ -64,7 +64,7 @@
|
||||
},
|
||||
{
|
||||
title: 'All in one platform',
|
||||
description: 'All the APIs a developer needs in one place. And more to come.',
|
||||
description: 'Everything you need to develop, deploy, and scale your applications.',
|
||||
icon: Expert
|
||||
}
|
||||
];
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils/classnames';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
'border-smooth relative -mb-24 border-t py-32',
|
||||
'bg-[url("/images/bgs/building-blocks.webp")] [background-size:50%] bg-right-bottom bg-no-repeat',
|
||||
'before:absolute before:top-0 before:left-0 before:z-0 before:block before:h-80 before:w-full before:bg-[radial-gradient(at_25%_0%,_hsla(343,_98%,_60%,_0.05)_0px,_transparent_73%,_transparent_100%)] md:before:w-1/2',
|
||||
'after:absolute after:top-0 after:right-0 after:z-0 after:hidden after:h-80 after:w-1/2 after:bg-[radial-gradient(at_100%_0%,_hsla(177,_53%,_69%,_0.1)_0px,_transparent_73%,_transparent_100%)] after:md:block md:after:block'
|
||||
)}
|
||||
>
|
||||
<div class="relative container grid grid-cols-1 place-items-center md:grid-cols-2">
|
||||
<section class="flex flex-col gap-4">
|
||||
<h2 class="text-display font-aeonik-pro text-primary max-w-[600px]">
|
||||
Become a Technology Partner
|
||||
</h2>
|
||||
<p class="text-body font-medium">
|
||||
Join our Technology Partners program to integrate your solutions with Appwrite’s
|
||||
API, enhancing functionality and expanding your reach.
|
||||
</p>
|
||||
<a href="/integrations/technology-partner" class="web-button mt-4">
|
||||
<span class="text">Get Started</span>
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
174
src/routes/partners/catalog/(components)/contact-partner.svelte
Normal file
@@ -0,0 +1,174 @@
|
||||
<script context="module" lang="ts">
|
||||
import { PUBLIC_GROWTH_ENDPOINT } from '$env/static/public';
|
||||
|
||||
export const subscribeToNewsletter = async (email: string) => {
|
||||
const response = await fetch(`${PUBLIC_GROWTH_ENDPOINT}/partners/contact`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email
|
||||
})
|
||||
});
|
||||
return response;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let name = '';
|
||||
let email = '';
|
||||
let companyName = '';
|
||||
let companyUrl = '';
|
||||
let message = '';
|
||||
|
||||
let submitted = false;
|
||||
let error: string | undefined;
|
||||
let submitting = false;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
submitting = true;
|
||||
error = undefined;
|
||||
const response = await subscribeToNewsletter(email);
|
||||
submitting = false;
|
||||
if (response.status >= 400) {
|
||||
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.';
|
||||
return;
|
||||
}
|
||||
submitted = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="relative max-h-[35vh]">
|
||||
<div class="relative z-10 py-10">
|
||||
<div class="mx-auto flex w-full max-w-4xl flex-col items-center gap-8 text-center">
|
||||
{#if submitted}
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="9"
|
||||
cy="9"
|
||||
r="8"
|
||||
fill="#FD366E"
|
||||
fill-opacity="0.08"
|
||||
stroke="#FD366E"
|
||||
stroke-opacity="0.32"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 10.5L7.75 12.5L12.75 6"
|
||||
stroke="#E4E4E7"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span class="text">
|
||||
Thank you for subscribing! An email has been sent to your inbox.
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<form
|
||||
method="post"
|
||||
on:submit|preventDefault={handleSubmit}
|
||||
class="mt-4 flex w-full flex-col gap-4 lg:mt-0"
|
||||
>
|
||||
<div class="flex w-full text-left">
|
||||
<ul class="web-form-list grid w-full gap-4 md:grid-cols-2">
|
||||
<li class="web-form-item">
|
||||
<label class="text-caption block" for="name">Full name</label>
|
||||
<input
|
||||
required
|
||||
class="web-input-text w-full"
|
||||
type="text"
|
||||
placeholder="Walter"
|
||||
id="name"
|
||||
bind:value={name}
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li class="web-form-item">
|
||||
<label class="text-caption block" for="workEmail">Email</label>
|
||||
<input
|
||||
required
|
||||
class="web-input-text w-full"
|
||||
type="email"
|
||||
placeholder="walter@appwrite.io"
|
||||
id="workEmail"
|
||||
bind:value={email}
|
||||
/>
|
||||
</li>
|
||||
<li class="web-form-item">
|
||||
<label class="text-caption block" for="companyName"
|
||||
>Company name</label
|
||||
>
|
||||
<input
|
||||
required
|
||||
class="web-input-text w-full"
|
||||
type="text"
|
||||
placeholder="Acme Corp"
|
||||
id="companyName"
|
||||
bind:value={companyName}
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li class="web-form-item flex-col gap-1">
|
||||
<label class="text-caption block" for="companyUrl"
|
||||
>Company url</label
|
||||
>
|
||||
<input
|
||||
required
|
||||
class="web-input-text w-full"
|
||||
type="url"
|
||||
placeholder="https://appwrite.io"
|
||||
id="companyUrl"
|
||||
bind:value={companyUrl}
|
||||
/>
|
||||
</li>
|
||||
<li class="web-form-item flex-col gap-1 sm:col-span-1 md:col-span-2">
|
||||
<label class="text-caption block" for="message"
|
||||
>Any other details you’d like to share?</label
|
||||
>
|
||||
<textarea
|
||||
required
|
||||
class="web-input-text w-full"
|
||||
id="message"
|
||||
placeholder="Your message"
|
||||
bind:value={message}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col items-center justify-between gap-4 sm:flex-col md:flex-col lg:flex-row"
|
||||
>
|
||||
<p class="text-caption max-w-sm self-start text-left lg:self-center">
|
||||
{#if error}
|
||||
{error}
|
||||
{:else}
|
||||
This form is protected by reCAPTCHA, and the Google Privacy Policy
|
||||
and Terms of Service apply.
|
||||
{/if}
|
||||
</p>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
class="web-button is-secondary cursor-pointer px-16!"
|
||||
>
|
||||
<span>Send</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
64
src/routes/partners/catalog/(components)/hero.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils/classnames';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
'grid-bg border-smooth relative flex items-center border-b px-5 py-28 lg:px-8 xl:px-16',
|
||||
'before:from-accent/20 before:absolute before:inset-0 before:-z-1 before:bg-linear-to-tr before:via-transparent before:via-40% before:to-transparent'
|
||||
)}
|
||||
>
|
||||
<div class="relative container pb-0">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex flex-col items-center justify-center gap-5 text-center">
|
||||
<div class="text-micro text-white uppercase">
|
||||
Appwrite Partner Catalog<span class="web-u-color-text-accent">_</span>
|
||||
</div>
|
||||
<h1 class="text-headline font-aeonik-pro text-primary">Find a Partner</h1>
|
||||
<p class="text-description max-w-xl">
|
||||
Unlock the full potential of Appwrite by seamlessly integrating your favorite
|
||||
apps with your projects.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.grid-bg {
|
||||
--line-color: rgba(255, 255, 255, 0.02);
|
||||
--size: calc(100vw / 16);
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
background-image:
|
||||
repeating-linear-gradient(
|
||||
0deg,
|
||||
var(--line-color),
|
||||
var(--line-color) 1px,
|
||||
transparent 1px,
|
||||
transparent var(--size)
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
90deg,
|
||||
var(--line-color),
|
||||
var(--line-color) 1px,
|
||||
transparent 1px,
|
||||
transparent var(--size)
|
||||
);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: radial-gradient(
|
||||
circle at bottom right,
|
||||
rgba(25, 25, 28, 0.5) 19%,
|
||||
transparent 100%
|
||||
);
|
||||
z-index: -2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
541
src/routes/partners/catalog/+page.svelte
Normal file
@@ -0,0 +1,541 @@
|
||||
<script lang="ts">
|
||||
import { Main } from '$lib/layouts';
|
||||
import { DEFAULT_HOST } from '$lib/utils/metadata';
|
||||
import { TITLE_SUFFIX } from '$routes/titles';
|
||||
import FooterNav from '$lib/components/FooterNav.svelte';
|
||||
import MainFooter from '$lib/components/MainFooter.svelte';
|
||||
import { type ResultType, Fuse } from '$lib/integrations';
|
||||
import { writable } from 'svelte/store';
|
||||
import { autoHash } from '$lib/actions/autoHash';
|
||||
import type { Partner } from './+page';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { classNames } from '$lib/utils/classnames';
|
||||
|
||||
import { page } from '$app/state';
|
||||
import Hero from './(components)/hero.svelte';
|
||||
import CallToAction from './(components)/call-to-action.svelte';
|
||||
import Input from '$lib/components/ui/input.svelte';
|
||||
import Icon from '$lib/components/ui/icon';
|
||||
|
||||
export let data;
|
||||
|
||||
const title = 'Partners' + TITLE_SUFFIX;
|
||||
const description =
|
||||
'Connect your favorite apps to Appwrite for one unified tech stack. Explore our catalog of integrations now.';
|
||||
const ogImage = DEFAULT_HOST + '/images/open-graph/website.png';
|
||||
|
||||
// search functionality
|
||||
let fuseOptions = {
|
||||
keys: ['title'],
|
||||
threshold: 0.2,
|
||||
distance: 500
|
||||
};
|
||||
|
||||
let result: ResultType<Partner> = [];
|
||||
|
||||
let hasQuery: boolean;
|
||||
let query = writable(decodeURIComponent(page.url.searchParams.get('search') ?? ''));
|
||||
|
||||
$: query.subscribe((value) => {
|
||||
hasQuery = value.length > 0;
|
||||
});
|
||||
|
||||
// platform filters
|
||||
const platforms = ['All', ...data.platforms];
|
||||
|
||||
let activePlatform = 'All';
|
||||
|
||||
// categories
|
||||
let activeCategory: string | null = null;
|
||||
|
||||
const handleQuery = (e: Event) => {
|
||||
const value = (e.currentTarget as HTMLInputElement).value;
|
||||
query.set(value);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if (browser) document.documentElement.setAttribute('data-scroll-smooth', '');
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (browser) document.documentElement.removeAttribute('data-scroll-smooth');
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<!-- Titles -->
|
||||
<title>{title}</title>
|
||||
<meta property="og:title" content={title} />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<!-- Desscription -->
|
||||
<meta name="description" content={description} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<!-- Image -->
|
||||
<meta property="og:image" content={ogImage} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta name="twitter:image" content={ogImage} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
</svelte:head>
|
||||
|
||||
<!-- binding for fuse -->
|
||||
<Fuse list={data.list} options={fuseOptions} bind:query={$query} bind:result />
|
||||
<Main>
|
||||
<Hero />
|
||||
|
||||
<div class="py-10">
|
||||
<div>
|
||||
<div class="container">
|
||||
<div class="l-integrations-grid">
|
||||
<aside class="sidebar flex flex-col gap-8" id="integrations-side">
|
||||
<section>
|
||||
<Input
|
||||
label="Search"
|
||||
name="search"
|
||||
placeholder="Search"
|
||||
bind:value={$query}
|
||||
autocomplete="off"
|
||||
oninput={handleQuery}
|
||||
>
|
||||
{#snippet icon()}
|
||||
<Icon name="search" aria-hidden="true" />
|
||||
{/snippet}
|
||||
</Input>
|
||||
</section>
|
||||
<section class="flex flex-col">
|
||||
<section class="flex flex-col gap-4">
|
||||
<h2
|
||||
class="web-side-nav-header text-micro whitespace-nowrap uppercase"
|
||||
>
|
||||
Platform
|
||||
</h2>
|
||||
<ul class="flex flex-wrap gap-2" class:disabled={hasQuery}>
|
||||
{#each platforms as platform}
|
||||
<li>
|
||||
<button
|
||||
class={classNames(
|
||||
'tag bg-greyscale-800 border-greyscale-700 h-8 cursor-pointer rounded-full border px-3 text-sm font-light',
|
||||
{
|
||||
'bg-white text-black':
|
||||
activePlatform === platform
|
||||
}
|
||||
)}
|
||||
class:active-tag={activePlatform === platform}
|
||||
on:click={() => (activePlatform = platform)}
|
||||
>{platform}</button
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
<div class="web-u-sep-block-start my-6"></div>
|
||||
<section class="flex flex-col gap-4">
|
||||
<h2
|
||||
class="web-side-nav-header text-micro whitespace-nowrap uppercase"
|
||||
>
|
||||
Categories
|
||||
</h2>
|
||||
|
||||
<div class="relative block sm:hidden">
|
||||
<select
|
||||
class="web-input-text w-full appearance-none"
|
||||
disabled={hasQuery}
|
||||
on:change={(e) =>
|
||||
goto(`#${e.currentTarget.value.toLowerCase()}`)}
|
||||
>
|
||||
{#each data.categories as category}
|
||||
{@const integrations = data.partners.find(
|
||||
(i) => i.category === category.slug
|
||||
)}
|
||||
{#if integrations && (activePlatform === 'All' || integrations.integrations.some( (i) => i.partnerLevel.includes(activePlatform) ))}
|
||||
<option value={category.slug}>
|
||||
{category.heading}
|
||||
</option>
|
||||
{/if}
|
||||
{/each}
|
||||
<option value={null}> Select category </option>
|
||||
</select>
|
||||
<Icon
|
||||
name="chevron-down"
|
||||
class="web-u-pointer-events-none absolute top-[11px] right-2"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul class="hidden flex-col gap-4 sm:flex" class:disabled={hasQuery}>
|
||||
{#each data.categories as category}
|
||||
{@const integrations = data.partners.find(
|
||||
(i) => i.category === category.slug
|
||||
)}
|
||||
{#if integrations && (activePlatform === 'All' || integrations.integrations.some( (i) => i.partnerLevel.includes(activePlatform) ))}
|
||||
<li>
|
||||
<a
|
||||
href={`#${category.slug}`}
|
||||
class="web-link"
|
||||
class:is-pink={category.slug === activeCategory}
|
||||
on:click={() =>
|
||||
activeCategory === category.slug}
|
||||
>{category.heading}</a
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<section>
|
||||
<div class="flex flex-col gap-16">
|
||||
{#if hasQuery}
|
||||
<section class="l-max-size-list-cards-section flex flex-col gap-8">
|
||||
<header class="flex flex-col gap-1">
|
||||
<h2 class="text-label text-primary">Search results</h2>
|
||||
<p class="text-description">
|
||||
{result.length > 0 ? result.length : 'No'} results found
|
||||
for "{$query}"
|
||||
</p>
|
||||
</header>
|
||||
<div class="l-max-size-list-cards flex flex-col gap-8">
|
||||
<ul class="l-grid-1">
|
||||
{#each result.map((d) => d.item) as item}
|
||||
<li>
|
||||
<a
|
||||
href={item.href}
|
||||
class="web-card is-normal h-full"
|
||||
style="--card-padding:1.5rem; --card-padding-mobile:1.5rem;"
|
||||
>
|
||||
<div
|
||||
class="mb-3 flex items-center justify-between"
|
||||
>
|
||||
<img
|
||||
class="web-user-box-image is-32px"
|
||||
src={item.product.avatar}
|
||||
alt={item.product.vendor}
|
||||
width="32"
|
||||
height="32"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
class="ml-auto"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h4 class="text-primary">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p class="text-sub-body mt-1">
|
||||
{item.description}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{:else}
|
||||
<section class="flex flex-col gap-8">
|
||||
<header class="flex flex-col gap-1">
|
||||
<h2 class="text-label text-primary">Featured</h2>
|
||||
<p class="text-description">Top recommended integrations</p>
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<ul class="web-feature-grid grid gap-4 sm:grid-cols-2">
|
||||
{#each data.featured as item}
|
||||
<li
|
||||
class="web-feature-grid-item is-two-columns-desktop-only relative"
|
||||
>
|
||||
<a
|
||||
class="block overflow-hidden rounded-2xl before:absolute before:inset-x-0 before:bottom-0 before:block before:h-80 before:rounded-[inherit] before:bg-gradient-to-b before:from-transparent before:via-transparent before:to-black"
|
||||
href={item.integration.href}
|
||||
>
|
||||
<img
|
||||
src={item.integration.cover}
|
||||
alt={item.integration.title}
|
||||
class="web-u-media-cover block aspect-video"
|
||||
/>
|
||||
<div
|
||||
class="web-user-box absolute bottom-4 left-4 z-10 gap-x-2"
|
||||
>
|
||||
<img
|
||||
class="row-span-2 block size-12 rounded-full"
|
||||
src={item.integration.product
|
||||
.avatar}
|
||||
alt={`Avatar for ${item.integration.product.vendor}`}
|
||||
width="40"
|
||||
height="40"
|
||||
/>
|
||||
<div
|
||||
class="text-body gap-2 font-medium"
|
||||
>
|
||||
<span class="text-primary mt-3">
|
||||
{item.integration.title}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="text-caption web-u-color-text-secondary"
|
||||
>
|
||||
{item.heading}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#each data.partners as { category, heading, description, integrations }}
|
||||
{#if integrations?.length > 0 && (activePlatform === 'All' || integrations.some( (i) => i.partnerLevel.includes(activePlatform) ))}
|
||||
<section
|
||||
class="l-max-size-list-cards-section flex flex-col gap-8"
|
||||
id={category.toLowerCase()}
|
||||
use:autoHash={(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (
|
||||
entry.isIntersecting &&
|
||||
activeCategory !== category.toLowerCase()
|
||||
) {
|
||||
activeCategory = category.toLowerCase();
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<header class="flex flex-col gap-1">
|
||||
<h2 class="text-label text-primary">
|
||||
{heading}
|
||||
</h2>
|
||||
<p class="text-description">
|
||||
{description}
|
||||
</p>
|
||||
</header>
|
||||
<div class="l-max-size-list-cards flex flex-col gap-8">
|
||||
<ul class="l-grid-1">
|
||||
{#each integrations as integration, index (`${integration.title}-${index}`)}
|
||||
{#if activePlatform === 'All' || integration.partnerLevel.includes(activePlatform)}
|
||||
<li>
|
||||
<a
|
||||
href={integration.href}
|
||||
class="web-card is-normal h-full"
|
||||
style="--card-padding:1.5rem; --card-padding-mobile:1.5rem; --card-border-radius: 1.5rem"
|
||||
>
|
||||
<div
|
||||
class="mb-3 flex items-center justify-between"
|
||||
>
|
||||
<img
|
||||
class="web-user-box-image is-32px"
|
||||
src={integration.product
|
||||
.avatar}
|
||||
alt={integration.product
|
||||
.vendor}
|
||||
width="32"
|
||||
height="32"
|
||||
/>
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
class="ml-auto"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h4 class="text-primary">
|
||||
{integration.title}
|
||||
</h4>
|
||||
|
||||
<p
|
||||
class="text-sub-body mt-1 line-clamp-2"
|
||||
>
|
||||
{integration.description}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
<a
|
||||
href={`#${category.toLowerCase()}`}
|
||||
class="l-float-button web-button is-text"
|
||||
>
|
||||
<span>Show more</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden pt-10">
|
||||
<CallToAction />
|
||||
<div class="container">
|
||||
<FooterNav />
|
||||
<MainFooter />
|
||||
</div>
|
||||
</div>
|
||||
</Main>
|
||||
|
||||
<style lang="scss">
|
||||
@use '$scss/abstract/functions' as f;
|
||||
@use '$scss/abstract/variables/devices';
|
||||
|
||||
:global([data-scroll-smooth]) {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.web-pre-footer-bg {
|
||||
position: absolute;
|
||||
top: clamp(300px, 50vw, 50%);
|
||||
left: clamp(300px, 50vw, 50%);
|
||||
transform: translate(-58%, -72%);
|
||||
width: clamp(1200px, 200vw, 3000px);
|
||||
height: auto;
|
||||
max-inline-size: unset;
|
||||
max-block-size: unset;
|
||||
}
|
||||
.l-float-button {
|
||||
display: none;
|
||||
}
|
||||
/* more tha 9 items */
|
||||
.l-max-size-list-cards-section {
|
||||
scroll-snap-align: start;
|
||||
scroll-margin-top: f.pxToRem(120);
|
||||
}
|
||||
.l-max-size-list-cards {
|
||||
&:where(:has(> ul > li:nth-child(10))) {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-height: f.pxToRem(350);
|
||||
content: '';
|
||||
display: block;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(25, 25, 28, 0) 0%,
|
||||
rgba(25, 25, 28, 0.92) 90%,
|
||||
#19191c 100%
|
||||
);
|
||||
transition: var(--transition);
|
||||
}
|
||||
.l-float-button {
|
||||
position: absolute;
|
||||
inset-inline: 0;
|
||||
inset-block-end: f.pxToRem(20);
|
||||
margin-inline: auto;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:where(:target) {
|
||||
.l-max-size-list-cards {
|
||||
overflow: visible;
|
||||
max-block-size: none;
|
||||
scroll-margin: 100px;
|
||||
&::before {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.l-float-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l-grid-1 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
|
||||
gap: 1rem;
|
||||
@media #{devices.$break1} {
|
||||
gap: 1.25rem;
|
||||
}
|
||||
}
|
||||
.l-integrations-grid {
|
||||
position: relative;
|
||||
|
||||
@media #{devices.$break1} {
|
||||
gap: 0;
|
||||
padding-block-start: f.pxToRem(80);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
& > li {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
margin-bottom: f.pxToRem(60);
|
||||
@media #{devices.$break2open} {
|
||||
position: sticky;
|
||||
top: 90px;
|
||||
height: 500px;
|
||||
transition: top 0.3s ease;
|
||||
|
||||
&.menu-visible {
|
||||
top: 122px;
|
||||
}
|
||||
}
|
||||
|
||||
.tag {
|
||||
min-width: f.pxToRem(42) !important;
|
||||
|
||||
&.active-tag {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media #{devices.$break2open} {
|
||||
display: grid;
|
||||
gap: f.pxToRem(68);
|
||||
grid-template-columns: f.pxToRem(240) 1fr;
|
||||
padding-block-start: f.pxToRem(40);
|
||||
}
|
||||
}
|
||||
.l-integrations-hero {
|
||||
@media #{devices.$break1} {
|
||||
}
|
||||
@media #{devices.$break2open} {
|
||||
}
|
||||
}
|
||||
.l-bg-1 {
|
||||
max-inline-size: none;
|
||||
max-block-size: none;
|
||||
inset-block-end: -600px;
|
||||
inset-inline-start: -600px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.l-bg-2 {
|
||||
inset-block-start: 0;
|
||||
inset-inline-start: 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.web-feature-grid {
|
||||
@media #{devices.$break1} {
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
.web-feature-grid {
|
||||
@media #{devices.$break1} {
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
src/routes/partners/catalog/+page.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { base } from '$app/paths';
|
||||
import { groupBy } from 'remeda';
|
||||
import {
|
||||
partnerCategoryDescriptions as categoryDescriptions,
|
||||
type SearchableCategory
|
||||
} from '$lib/constants';
|
||||
|
||||
export type Partner = {
|
||||
title: string;
|
||||
description: string;
|
||||
featured?: boolean;
|
||||
cover: string;
|
||||
partnerLevel: 'Bronze' | 'Silver' | 'Gold' | 'Platinum';
|
||||
category: string;
|
||||
frameworks: Array<string>;
|
||||
website: string;
|
||||
href: string;
|
||||
product: {
|
||||
vendor: string;
|
||||
avatar: string;
|
||||
href: string;
|
||||
};
|
||||
capabilities: Array<string>;
|
||||
regions: Array<string>;
|
||||
languages: Array<string>;
|
||||
};
|
||||
|
||||
export const load = () => {
|
||||
const partnersGlob = import.meta.glob('./**/*.markdoc', {
|
||||
eager: true
|
||||
});
|
||||
|
||||
const categories: Array<SearchableCategory> = [];
|
||||
const platforms: string[] = [];
|
||||
|
||||
const partners = Object.entries(partnersGlob).map(([filepath, integrationList]) => {
|
||||
const { frontmatter } = integrationList as {
|
||||
frontmatter: Partner;
|
||||
};
|
||||
|
||||
const slug = filepath.replace('./', '').replace('/+page.markdoc', '');
|
||||
const integrationName = slug.slice(slug.lastIndexOf('/') + 1);
|
||||
|
||||
categories.push(
|
||||
categoryDescriptions.find((i) => i.slug === frontmatter.category) ??
|
||||
({} as SearchableCategory)
|
||||
);
|
||||
|
||||
return {
|
||||
...frontmatter,
|
||||
href: `${base}/partners/catalog/${integrationName}`
|
||||
};
|
||||
});
|
||||
|
||||
const groupedPartners = groupBy(partners, (i) => i.category);
|
||||
|
||||
const partnersWithDescriptions = Object.entries(groupedPartners).map(
|
||||
([category, integrations]) => {
|
||||
const integrationCategory = categoryDescriptions.find(
|
||||
(key) => key.slug === category.toLowerCase()
|
||||
);
|
||||
return {
|
||||
category,
|
||||
heading: integrationCategory?.heading,
|
||||
description: integrationCategory?.description,
|
||||
integrations
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const featuredIntegrations = partners.filter((i) => i.featured);
|
||||
|
||||
const featuredIntegrationsWithCategoryHeadings = Object.entries(featuredIntegrations).map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
([_, integration]) => {
|
||||
const integrationCategory = categoryDescriptions.find(
|
||||
(key) => key.slug === integration.category.toLowerCase()
|
||||
);
|
||||
return {
|
||||
heading: integrationCategory?.heading,
|
||||
integration
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
partners: partnersWithDescriptions,
|
||||
list: partners,
|
||||
categories: new Set(categories),
|
||||
platforms: new Set(platforms),
|
||||
featured: featuredIntegrationsWithCategoryHeadings
|
||||
};
|
||||
};
|
||||
|
||||
export const prerender = false;
|
||||
56
src/routes/partners/catalog/fraqtory/+page.markdoc
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
layout: partner
|
||||
title: Fraqtory
|
||||
description: Transform your startup vision into reality with custom software and Fractional CTO services, tailored to accelerate growth and success.
|
||||
date: 2025-03-21
|
||||
featured: false
|
||||
partnerLevel: Silver
|
||||
isNew: true
|
||||
cover: /images/partners/covers/fraqtory.png
|
||||
category: agency
|
||||
website:
|
||||
product:
|
||||
avatar: '/images/partners/avatars/fraqtory.png'
|
||||
vendor: Fraqtory
|
||||
description: Transform your startup vision into reality with custom software and Fractional CTO services, tailored to accelerate growth and success.
|
||||
frameworks:
|
||||
- Javascript
|
||||
- Typescript
|
||||
- React
|
||||
- Next.js
|
||||
- Flutter
|
||||
- React Native
|
||||
- PHP,
|
||||
- Java
|
||||
- Kotlin
|
||||
- Python
|
||||
- C
|
||||
- .NET
|
||||
- Spring Boot
|
||||
- Laravel
|
||||
- MySQL
|
||||
- PostgreSQL
|
||||
capabilities:
|
||||
- Fractional CTO
|
||||
- Software House
|
||||
- Mobile App Development
|
||||
- Web App Development
|
||||
regions:
|
||||
- Africa
|
||||
- Europe
|
||||
- South America
|
||||
- Mid America
|
||||
- North America
|
||||
languages:
|
||||
- English
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Transform your startup vision into reality with custom software and Fractional CTO services, tailored to accelerate growth and success.
|
||||
|
||||
# Services
|
||||
|
||||
We use Appwrite to accelerate the development of new technology for our clients, primarily tech-based startups. It also enables us to rapidly build MVPs and event platforms, ensuring speed and efficiency in execution.
|
||||
|
||||
Our ideal projects involve collaborating with startups that need to develop innovative solutions from the ground up, mainly focusing on computer vision, full-stack and cloud computing areas.
|
||||
43
src/routes/partners/catalog/makeshyft/+page.markdoc
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
layout: partner
|
||||
title: MakeShyft
|
||||
description: MakeShyft is a software company currently transitioning from client work to B2C & B2B software products. Our goal is to build productivity, communication and collaboration tools. We have big goals and dreams and we're just getting started.
|
||||
date: 2025-03-21
|
||||
featured: true
|
||||
partnerLevel: Silver
|
||||
isNew: true
|
||||
cover: /images/partners/covers/makeshyft.png
|
||||
category: agency
|
||||
website: http://www.makeshyft.com
|
||||
product:
|
||||
avatar: '/images/partners/avatars/makeshyft.png'
|
||||
vendor: SimbaLabs
|
||||
description: MakeShyft is a software company currently transitioning from client work to B2C & B2B software products. Our goal is to build productivity, communication and collaboration tools. We have big goals and dreams and we're just getting started.
|
||||
frameworks:
|
||||
- Livecode
|
||||
- Javascript
|
||||
- PHP
|
||||
capabilities:
|
||||
- Web
|
||||
regions:
|
||||
- Africa
|
||||
- Asia
|
||||
- Australia
|
||||
- New Zealand
|
||||
- Europe
|
||||
- Latin America
|
||||
- Middle East
|
||||
- North America
|
||||
languages:
|
||||
- English
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
MakeShyft is a software company currently transitioning from client work to B2C & B2B software products. Our goal is to build productivity, communication and collaboration tools. We have big goals and dreams and we are just getting started.
|
||||
|
||||
# Services
|
||||
|
||||
We develop software products, we do not currently accept external projects.
|
||||
|
||||
Useful desktop applications that run native and local first.
|
||||
43
src/routes/partners/catalog/mohesu-enterprises/+page.markdoc
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
layout: partner
|
||||
title: Mohesu Enterprises
|
||||
description: Mohesu specializes in creating bespoke digital solutions—from responsive websites and mobile applications to comprehensive managed cloud services, AI & machine learning solutions, and digital marketing strategies. Their approach emphasizes clean, user-friendly interfaces and scalable, customizable technology stacks utilizing frameworks like Next.js, Flutter, and Angular with Appwrite.
|
||||
date: 2025-03-21
|
||||
featured: false
|
||||
partnerLevel: Silver
|
||||
isNew: true
|
||||
cover: /images/partners/covers/mohesu.png
|
||||
category: agency
|
||||
website: https://mohesu.com/
|
||||
product:
|
||||
avatar: '/images/partners/avatars/mohesu.png'
|
||||
vendor: SimbaLabs
|
||||
description: 'Mohesu specializes in creating bespoke digital solutions—from responsive websites and mobile applications to comprehensive managed cloud services, AI & machine learning solutions, and digital marketing strategies. Their approach emphasizes clean, user-friendly interfaces and scalable, customizable technology stacks utilizing frameworks like Next.js, Flutter, and Angular with Appwrite.'
|
||||
frameworks:
|
||||
- Flutter
|
||||
- NextJS
|
||||
- Angular
|
||||
- Remix
|
||||
- Express.js
|
||||
- Dart
|
||||
- Typescript
|
||||
- Javascript
|
||||
- Python
|
||||
capabilities:
|
||||
- AI
|
||||
- Web
|
||||
- Mobile
|
||||
regions:
|
||||
- India
|
||||
languages:
|
||||
- English
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Mohesu specializes in creating bespoke digital solutions—from responsive websites and mobile applications to comprehensive managed cloud services, AI & machine learning solutions, and digital marketing strategies. Their approach emphasizes clean, user-friendly interfaces and scalable, customizable technology stacks utilizing frameworks like Next.js, Flutter, and Angular with Appwrite.
|
||||
|
||||
# Services
|
||||
We specialize in web and mobile app development, managed cloud services, as well as SEO and digital marketing to help you build, scale, and grow your digital presence.
|
||||
|
||||
We rely on Appwrite as the backend server for our web and mobile applications, ensuring secure, scalable, and efficient development.
|
||||
42
src/routes/partners/catalog/nanornia/+page.markdoc
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
layout: partner
|
||||
title: Nanornia
|
||||
description: Nanornia is a digital agency that creates websites, apps, and visual designs. We help businesses of all sizes build professional and user-friendly digital solutions, using both custom development and low-code tools to deliver high-quality results.
|
||||
date: 2025-03-21
|
||||
featured: true
|
||||
partnerLevel: Silver
|
||||
isNew: true
|
||||
cover: /images/partners/covers/nanornia.png
|
||||
category: agency
|
||||
website: https://nanornia.se
|
||||
product:
|
||||
avatar: '/images/partners/avatars/nanornia.png'
|
||||
vendor: nanornia
|
||||
description: 'Nanornia is a digital agency that creates websites, apps, and visual designs. We help businesses of all sizes build professional and user-friendly digital solutions, using both custom development and low-code tools to deliver high-quality results.'
|
||||
frameworks:
|
||||
- Astro
|
||||
- Nuxt
|
||||
- Vue
|
||||
- Flutter
|
||||
capabilities:
|
||||
- AI
|
||||
- Web
|
||||
- Mobile
|
||||
regions:
|
||||
- Europe
|
||||
languages:
|
||||
- English
|
||||
- Swedish
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Nanornia is a digital agency that creates websites, apps, and visual designs. We help businesses of all sizes build professional and user-friendly digital solutions, using both custom development and low-code tools to deliver high-quality results.
|
||||
|
||||
# Services
|
||||
|
||||
We provide end-to-end services in web development, app creation, and visual design, building everything from websites and digital platforms to fully custom applications tailored to your business needs.
|
||||
|
||||
We use Appwrite to power secure and scalable web and mobile apps, handling everything from user authentication and databases to file storage and server-side functions—accelerating development and boosting efficiency.
|
||||
|
||||
Our sweet spot is building SaaS platforms, web apps, and AI-powered solutions for businesses that need reliable, scalable, and high-performance digital tools.
|
||||
48
src/routes/partners/catalog/panara-studio/+page.markdoc
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
layout: partner
|
||||
title: Panra Studio
|
||||
description: Panara Studio started as a one-person agency in 2021 and has grown into a small team of experts. We've worked with clients from 10+ countries, including startups, early-stage ventures, angel investors, and creators. Collaborate directly with Kamal Panara, the founder and engineer, and our talented professionals.
|
||||
date: 2025-03-21
|
||||
featured: false
|
||||
partnerLevel: Silver
|
||||
isNew: true
|
||||
cover: /images/partners/covers/panara.png
|
||||
category: agency
|
||||
website: https://panara.studio
|
||||
product:
|
||||
avatar: '/images/partners/avatars/panara.png'
|
||||
vendor: Panara Studio
|
||||
description: Panara Studio started as a one-person agency in 2021 and has grown into a small team of experts. We've worked with clients from 10+ countries, including startups, early-stage ventures, angel investors, and creators. Collaborate directly with Kamal Panara, the founder and engineer, and our talented professionals.
|
||||
frameworks:
|
||||
- Flutter
|
||||
- ReactJS
|
||||
- NextJS
|
||||
- Typescript
|
||||
- Dart
|
||||
- Javascript
|
||||
- NodeJS
|
||||
capabilities:
|
||||
- UI/UX Design
|
||||
- Mobile App Development
|
||||
- Web App Development
|
||||
regions:
|
||||
- Africa
|
||||
- India
|
||||
- Australia
|
||||
- Europe
|
||||
- South America
|
||||
- Mid America
|
||||
- North America
|
||||
languages:
|
||||
- English
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Panara Studio started as a one-person agency in 2021 and has grown into a small team of experts. We've worked with clients from 10+ countries, including startups, early-stage ventures, angel investors, and creators. Collaborate directly with Kamal Panara, the founder and engineer, and our talented professionals.
|
||||
|
||||
# Services
|
||||
|
||||
At Panera Studio, we specialize in UI/UX design, mobile app development, and web app development, creating high-quality digital products tailored to your needs. Whether it’s an e-commerce app, e-learning platform, CRM system, or a fully bespoke application, we bring ideas to life with a strong focus on performance, scalability, and user experience.
|
||||
|
||||
We use Appwrite as the backend foundation for our internal tools, SaaS products, and client projects, ensuring modern, secure, and efficient development every step of the way.
|
||||
43
src/routes/partners/catalog/simbalabs/+page.markdoc
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
layout: partner
|
||||
title: SimbaLabs
|
||||
description: We are a web engineering company focused on making scalable, efficient web and mobile experiences.
|
||||
date: 2025-03-21
|
||||
featured: false
|
||||
partnerLevel: Silver
|
||||
isNew: true
|
||||
cover: /images/partners/covers/simbalabs.png
|
||||
category: agency
|
||||
website: www.simbalabs.co.za
|
||||
product:
|
||||
avatar: '/images/partners/avatars/simbalabs.png'
|
||||
vendor: SimbaLabs
|
||||
description: 'We are a web engineering company focused on making scalable, efficient web and mobile experiences.'
|
||||
frameworks:
|
||||
- Javascript
|
||||
- Python
|
||||
- Swift
|
||||
capabilities:
|
||||
- AI
|
||||
- Web
|
||||
- Mobile
|
||||
regions:
|
||||
- Africa
|
||||
- Asia
|
||||
- Australia
|
||||
- New Zealand
|
||||
- Europe
|
||||
- Latin America
|
||||
- Middle East
|
||||
- North America
|
||||
languages:
|
||||
- English
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
We are a web engineering company focused on making scalable, efficient web and mobile experiences.
|
||||
|
||||
# Services
|
||||
|
||||
We build AI, web, and mobile applications for clients. We use Appwrite Databases, Functions, Auth & Storage to build innovative, scalable AI, web, and mobile apps.
|
||||
15
src/routes/products/auth/(components)/(snippets)/astro.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Client, Account } from 'node-appwrite';
|
||||
|
||||
async function getLoggedInUser(context) {
|
||||
const session = cookies().get('custom-session-cookie');
|
||||
if (!session) return;
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT_ID);
|
||||
|
||||
client.setSession(session.value);
|
||||
const account = new Account(client);
|
||||
|
||||
return account.get();
|
||||
}
|
||||
16
src/routes/products/auth/(components)/(snippets)/nextjs.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Client, Account } from 'node-appwrite';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
async function getLoggedInUser() {
|
||||
const session = cookies().get('custom-session-cookie');
|
||||
if (!session) return;
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID);
|
||||
|
||||
client.setSession(session.value);
|
||||
const account = new Account(client);
|
||||
|
||||
return account.get();
|
||||
}
|
||||
16
src/routes/products/auth/(components)/(snippets)/nuxt.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Client, Account } from 'node-appwrite';
|
||||
import { H3Event, getCookie } from 'h3';
|
||||
|
||||
async function getLoggedInUser(event) {
|
||||
const session = getCookie(event, 'custom-session-cookie');
|
||||
if (!session) return;
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID);
|
||||
|
||||
client.setSession(session.value);
|
||||
const account = new Account(client);
|
||||
|
||||
return account.get();
|
||||
}
|
||||
21
src/routes/products/auth/(components)/(snippets)/remix.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Client, Account } from 'node-appwrite';
|
||||
import { createCookie } from '@remix-run/node';
|
||||
|
||||
export const customSessionCookie = createCookie('custom-session-cookie', {
|
||||
maxAge: 604800,
|
||||
});
|
||||
|
||||
async function getLoggedInUser(request) {
|
||||
const cookies = request.headers.get('Cookie');
|
||||
const session = await customSessionCookie.parse(cookies):
|
||||
if (!session) return;
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID);
|
||||
|
||||
client.setSession(session.value);
|
||||
const account = new Account(client);
|
||||
|
||||
return await account.get();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Client, Account } from 'node-appwrite';
|
||||
|
||||
async function getLoggedInUser() {
|
||||
const session = cookies().get('custom-session-cookie');
|
||||
if (!session) return;
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID);
|
||||
|
||||
client.setSession(session.value);
|
||||
const account = new Account(client);
|
||||
|
||||
return account.get();
|
||||
}
|
||||
@@ -1,117 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { Tooltip } from '$lib/components';
|
||||
import { Button } from '$lib/components/ui';
|
||||
import { Framework, Platform } from '$lib/utils/references';
|
||||
import MultiFrameworkCode from './MultiFrameworkCode.svelte';
|
||||
import SnippetNextJs from './(snippets)/nextjs.txt';
|
||||
import SnippetSvelteKit from './(snippets)/sveltekit.txt';
|
||||
import SnippetAstro from './(snippets)/astro.txt';
|
||||
import SnippetNuxt from './(snippets)/nuxt.txt';
|
||||
import SnippetRemix from './(snippets)/remix.txt';
|
||||
|
||||
const codeSnippets = [
|
||||
{
|
||||
language: Platform.ClientWeb,
|
||||
platform: Framework.NextJs,
|
||||
content: `import { Client, Account } from 'node-appwrite'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
async function getLoggedInUser() {
|
||||
const session = cookies().get("custom-session-cookie");
|
||||
if (!session) return
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID)
|
||||
|
||||
client.setSession(session.value)
|
||||
const account = new Account(client);
|
||||
|
||||
return await account.get()
|
||||
}
|
||||
`
|
||||
content: SnippetNextJs
|
||||
},
|
||||
{
|
||||
language: Platform.ClientWeb,
|
||||
platform: Framework.SvelteKit,
|
||||
content: `import { Client, Account } from 'node-appwrite'
|
||||
|
||||
async function getLoggedInUser() {
|
||||
const session = cookies().get("custom-session-cookie");
|
||||
if (!session) return
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID)
|
||||
|
||||
client.setSession(session.value)
|
||||
const account = new Account(client);
|
||||
|
||||
return await account.get()
|
||||
}
|
||||
`
|
||||
content: SnippetSvelteKit
|
||||
},
|
||||
{
|
||||
language: Platform.ClientWeb,
|
||||
platform: Framework.Astro,
|
||||
content: `import { Client, Account } from 'node-appwrite'
|
||||
|
||||
async function getLoggedInUser(context) {
|
||||
const session = cookies().get("custom-session-cookie");
|
||||
if (!session) return
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT_ID)
|
||||
|
||||
client.setSession(session.value)
|
||||
const account = new Account(client);
|
||||
|
||||
return await account.get()
|
||||
}
|
||||
`
|
||||
content: SnippetAstro
|
||||
},
|
||||
{
|
||||
language: Platform.ClientWeb,
|
||||
platform: Framework.Nuxt3,
|
||||
content: `import { Client, Account } from 'node-appwrite'
|
||||
import { H3Event, getCookie } from 'h3'
|
||||
|
||||
async function getLoggedInUser(event) {
|
||||
const session = getCookie(event, "custom-session-cookie");
|
||||
if (!session) return
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID)
|
||||
|
||||
client.setSession(session.value)
|
||||
const account = new Account(client);
|
||||
|
||||
return await account.get()
|
||||
}`
|
||||
content: SnippetNuxt
|
||||
},
|
||||
{
|
||||
language: Platform.ClientWeb,
|
||||
platform: Framework.Remix,
|
||||
content: `import { Client, Account } from 'node-appwrite'
|
||||
import { createCookie } from '@remix-run/node'
|
||||
|
||||
export const customSessionCookie = createCookie("custom-session-cookie", {
|
||||
maxAge: 604800,
|
||||
})
|
||||
|
||||
async function getLoggedInUser(request) {
|
||||
const cookies = request.headers.get("Cookie")
|
||||
const session = await customSessionCookie.parse(cookies)
|
||||
if (!session) return
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(process.env.PUBLIC_APPWRITE_PROJECT_ID)
|
||||
|
||||
client.setSession(session.value)
|
||||
const account = new Account(client);
|
||||
|
||||
return await account.get()
|
||||
}`
|
||||
content: SnippetRemix
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace DotNetRuntime;
|
||||
|
||||
using Appwrite;
|
||||
using Appwrite.Services;
|
||||
using Appwrite.Models;
|
||||
|
||||
public class Handler {
|
||||
|
||||
// This is your Appwrite function
|
||||
// It"s executed each time we get a request
|
||||
public async Task Main(RuntimeContext Context)
|
||||
{
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// var client = new Client()
|
||||
// .SetProject(Environment.GetEnvironmentVariable("APPWRITE_FUNCTION_PROJECT_ID"))
|
||||
// .SetKey(Context.Req.Headers["x-appwrite-key"]);
|
||||
|
||||
// You can log messages to the console
|
||||
Context.Log("Hello, Logs!");
|
||||
|
||||
// If something goes wrong, log an error
|
||||
Context.Error("Hello, Errors!");
|
||||
|
||||
// The 'Context.Req' object contains the request data
|
||||
if (Context.Req.Method == "GET") {
|
||||
// Send a response with the res object helpers
|
||||
// 'Context.Res.Text()' dispatches a string back to the client
|
||||
return Context.Res.Text("Hello, World!");
|
||||
}
|
||||
|
||||
// 'Context.Res.Json()' is a handy helper for sending JSON
|
||||
return Context.Res.Json(new Dictionary()
|
||||
{
|
||||
{ "motto", "Build like a team of hundreds_" },
|
||||
{ "learn", "https://appwrite.io/docs" },
|
||||
{ "connect", "https://appwrite.io/discord" },
|
||||
{ "getInspired", "https://builtwith.appwrite.io" },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'dart:async';
|
||||
import 'package:dart_appwrite/dart_appwrite.dart';
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
Future main(final context) async {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// final client = Client()
|
||||
// .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])
|
||||
// .setKey(context.req.headers['x-appwrite-key']);
|
||||
|
||||
|
||||
// You can log messages to the console
|
||||
context.log('Hello, Logs!');
|
||||
|
||||
// If something goes wrong, log an error
|
||||
context.error('Hello, Errors!');
|
||||
|
||||
// The 'req' object contains the request data
|
||||
if (context.req.method == 'GET') {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return context.res.text('Hello, World!');
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return context.res.json({
|
||||
'motto': 'Build like a team of hundreds_',
|
||||
'learn': 'https://appwrite.io/docs',
|
||||
'connect': 'https://appwrite.io/discord',
|
||||
'getInspired': 'https://builtwith.appwrite.io',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Client } from "https://deno.land/x/appwrite@7.0.0/mod.ts";
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
export default ({ req, res, log, error }: any) => {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// const client = new Client()
|
||||
// .setProject(Deno.env.get("APPWRITE_FUNCTION_PROJECT_ID") || "")
|
||||
// .setKey(req.headers["x-appwrite-key"] || "");
|
||||
|
||||
// You can log messages to the console
|
||||
log("Hello, Logs!");
|
||||
|
||||
// If something goes wrong, log an error
|
||||
error("Hello, Errors!");
|
||||
|
||||
// The 'req' object contains the request data
|
||||
if (req.method === "GET") {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return res.text("Hello, World!");
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return res.json({
|
||||
motto: "Build like a team of hundreds_",
|
||||
learn: "https://appwrite.io/docs",
|
||||
connect: "https://appwrite.io/discord",
|
||||
getInspired: "https://builtwith.appwrite.io",
|
||||
});
|
||||
};
|
||||
45
src/routes/products/functions/(components)/(snippets)/go.txt
Normal file
@@ -0,0 +1,45 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"github.com/appwrite/sdk-for-go/appwrite"
|
||||
"github.com/open-runtimes/types-for-go/v4/openruntimes"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Motto string `json:"motto"`
|
||||
Learn string `json:"learn"`
|
||||
Connect string `json:"connect"`
|
||||
GetInspired string `json:"getInspired"`
|
||||
}
|
||||
|
||||
func Main(Context openruntimes.Context) openruntimes.Response {
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request service
|
||||
var _ = appwrite.NewClient(
|
||||
appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")),
|
||||
appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]),
|
||||
)
|
||||
|
||||
// You can log messages to the console
|
||||
fmt.Println("Hello, Logs!")
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Error:", "Hello, Errors!")
|
||||
|
||||
// The 'Context.Req' object contains the request data
|
||||
if Context.Req.Method == "GET" {
|
||||
// Send a response with the Context.Res object helpers
|
||||
// 'Context.Res.Text()' dispatches a string back to the client
|
||||
return Context.Res.Text("Hello, World!")
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return Context.Res.Json(
|
||||
Response{
|
||||
Motto: "Build like a team of hundreds_",
|
||||
Learn: "https://appwrite.io/docs",
|
||||
Connect: "https://appwrite.io/discord",
|
||||
GetInspired: "https://builtwith.appwrite.io",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.openruntimes.java.src;
|
||||
|
||||
import io.openruntimes.java.RuntimeContext;
|
||||
import io.openruntimes.java.RuntimeOutput;
|
||||
import java.util.HashMap;
|
||||
import io.appwrite.Client;
|
||||
|
||||
public class Main {
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
public RuntimeOutput main(RuntimeContext context) throws Exception {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// Client client = new Client();
|
||||
// .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID"))
|
||||
// .setKey(context.getReq().getHeaders().get("x-appwrite-key"));
|
||||
|
||||
// You can log messages to the console
|
||||
context.log("Hello, Logs!");
|
||||
|
||||
// If something goes wrong, log an error
|
||||
context.error("Hello, Errors!");
|
||||
|
||||
// The 'context.getReq()' object contains the request data
|
||||
if (context.getReq().getMethod().equals("GET")) {
|
||||
// Send a response with the res object helpers
|
||||
// 'context.getRes().text()' dispatches a string back to the client
|
||||
return context.getRes().text("Hello, World!");
|
||||
}
|
||||
|
||||
Map json = new HashMap<>();
|
||||
json.put("motto", "Build like a team of hundreds_");
|
||||
json.put("learn", "https://appwrite.io/docs");
|
||||
json.put("connect", "https://appwrite.io/discord");
|
||||
json.put("getInspired", "https://builtwith.appwrite.io");
|
||||
|
||||
// 'context.getRes().json()' is a handy helper for sending JSON
|
||||
return context.getRes().json(json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.openruntimes.kotlin.src
|
||||
|
||||
import io.openruntimes.kotlin.RuntimeContext
|
||||
import io.openruntimes.kotlin.RuntimeOutput
|
||||
import io.appwrite.Client
|
||||
import java.util.HashMap
|
||||
|
||||
class Main {
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
fun main(context: RuntimeContext): RuntimeOutput {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// val client = Client()
|
||||
// .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID"))
|
||||
// .setKey(context.req.headers["x-appwrite-key"])
|
||||
|
||||
// You can log messages to the console
|
||||
context.log("Hello, Logs!")
|
||||
|
||||
// If something goes wrong, log an error
|
||||
context.error("Hello, Errors!")
|
||||
|
||||
// The 'context.req' object contains the request data
|
||||
if (context.req.method == "GET") {
|
||||
// Send a response with the res object helpers
|
||||
// 'context.res.text()' dispatches a string back to the client
|
||||
return context.res.text("Hello, World!")
|
||||
}
|
||||
|
||||
// 'context.res.json()' is a handy helper for sending JSON
|
||||
return context.res.json(mutableMapOf(
|
||||
"motto" to "Build like a team of hundreds_",
|
||||
"learn" to "https://appwrite.io/docs",
|
||||
"connect" to "https://appwrite.io/discord",
|
||||
"getInspired" to "https://builtwith.appwrite.io"
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Client } from 'node-appwrite';
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
export default async ({ req, res, log, error }) => {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// const client = new Client()
|
||||
// .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
|
||||
// .setKey(req.headers['x-appwrite-key']);
|
||||
|
||||
// You can log messages to the console
|
||||
log('Hello, Logs!');
|
||||
|
||||
// If something goes wrong, log an error
|
||||
error('Hello, Errors!');
|
||||
|
||||
// The 'req' object contains the request data
|
||||
if (req.method === 'GET') {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return res.text('Hello, World!');
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return res.json({
|
||||
motto: 'Build like a team of hundreds_',
|
||||
learn: 'https://appwrite.io/docs',
|
||||
connect: 'https://appwrite.io/discord',
|
||||
getInspired: 'https://builtwith.appwrite.io',
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
require(__DIR__ . '/../vendor/autoload.php');
|
||||
|
||||
use Appwrite\Client;
|
||||
use Appwrite\Exception;
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
return function ($context) {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// $client = (new Client())
|
||||
// ->setProject(getenv(APPWRITE_FUNCTION_PROJECT_ID))
|
||||
// ->setKey($context->req->headers['x-appwrite-key']);
|
||||
|
||||
// You can log messages to the console
|
||||
$context->log('Hello, Logs!');
|
||||
|
||||
// If something goes wrong, log an error
|
||||
$context->error('Hello, Errors!');
|
||||
|
||||
// The 'req' object contains the request data
|
||||
if ($context->req->method === 'GET') {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return $context->res->text('Hello, World!');
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return $context->res->json([
|
||||
'motto' => 'Build like a team of hundreds_',
|
||||
'learn' => 'https://appwrite.io/docs',
|
||||
'connect' => 'https://appwrite.io/discord',
|
||||
'getInspired' => 'https://builtwith.appwrite.io',
|
||||
]);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
from appwrite.client import Client
|
||||
import os
|
||||
|
||||
# This is your Appwrite function
|
||||
# It's executed each time we get a request
|
||||
def main(context):
|
||||
# Why not try the Appwrite SDK?
|
||||
#
|
||||
# Set project and set API key
|
||||
# client = (
|
||||
# Client()
|
||||
# .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"])
|
||||
# .set_key(context.req.headers["x-appwrite-key"])
|
||||
# )
|
||||
|
||||
# You can log messages to the console
|
||||
context.log("Hello, Logs!")
|
||||
|
||||
# If something goes wrong, log an error
|
||||
context.error("Hello, Errors!")
|
||||
|
||||
# The 'context.req' object contains the request data
|
||||
if context.req.method == "GET":
|
||||
# Send a response with the res object helpers
|
||||
# 'context.res.text()' dispatches a string back to the client
|
||||
return context.res.text("Hello, World!")
|
||||
|
||||
# 'context.res.json()' is a handy helper for sending JSON
|
||||
return context.res.json({
|
||||
"motto": "Build like a team of hundreds_",
|
||||
"learn": "https://appwrite.io/docs",
|
||||
"connect": "https://appwrite.io/discord",
|
||||
"getInspired": "https://builtwith.appwrite.io",
|
||||
})
|
||||
@@ -0,0 +1,33 @@
|
||||
require "appwrite"
|
||||
|
||||
# This is your Appwrite function
|
||||
# It's executed each time we get a request
|
||||
def main(context)
|
||||
# Why not try the Appwrite SDK?
|
||||
#
|
||||
# Set project and set API key
|
||||
# client = Client.new
|
||||
# .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])
|
||||
# .set_key(context.req.headers['x-appwrite-key'])
|
||||
|
||||
# You can log messages to the console
|
||||
context.log("Hello, Logs!")
|
||||
|
||||
# If something goes wrong, log an error
|
||||
context.error("Hello, Errors!")
|
||||
|
||||
# The 'context.req' object contains the request data
|
||||
if (context.req.method == "GET")
|
||||
# Send a response with the res object helpers
|
||||
# 'context.res.text()' dispatches a string back to the client
|
||||
return context.res.text("Hello, World!")
|
||||
end
|
||||
|
||||
# 'context.res.json()' is a handy helper for sending JSON
|
||||
return context.res.json({
|
||||
"motto": "Build like a team of hundreds_",
|
||||
"learn": "https://appwrite.io/docs",
|
||||
"connect": "https://appwrite.io/discord",
|
||||
"getInspired": "https://builtwith.appwrite.io",
|
||||
})
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
import Appwrite
|
||||
import AppwriteModels
|
||||
import Foundation
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
func main(context: RuntimeContext) async throws -> RuntimeOutput {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// let client = Client()
|
||||
// .setProject(ProcessInfo.processInfo.environment["APPWRITE_FUNCTION_PROJECT_ID"])
|
||||
// .setKey(context.req.headers["x-appwrite-key"] ?? "")
|
||||
|
||||
// You can log messages to the console
|
||||
context.log("Hello, Logs!")
|
||||
|
||||
// If something goes wrong, log an error
|
||||
context.error("Hello, Errors!")
|
||||
|
||||
// The 'context.req' object contains the request data
|
||||
if context.req.method == "GET" {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return context.res.text("Hello, World!")
|
||||
}
|
||||
|
||||
// 'context.res.json()' is a handy helper for sending JSON
|
||||
return try context.res.json([
|
||||
"motto": "Build like a team of hundreds_",
|
||||
"learn": "https://appwrite.io/docs",
|
||||
"connect": "https://appwrite.io/discord",
|
||||
"getInspired": "https://builtwith.appwrite.io",
|
||||
])
|
||||
}
|
||||
@@ -2,463 +2,65 @@
|
||||
import { Button } from '$lib/components/ui';
|
||||
import { Platform } from '$lib/utils/references';
|
||||
import MultiCodeContextless from '$routes/products/messaging/(components)/MultiCodeContextless.svelte';
|
||||
import SnippetNodejs from './(snippets)/nodejs.txt';
|
||||
import SnippetPhp from './(snippets)/php.txt';
|
||||
import SnippetPython from './(snippets)/python.txt';
|
||||
import SnippetRuby from './(snippets)/ruby.txt';
|
||||
import SnippetDeno from './(snippets)/deno.txt';
|
||||
import SnippetGo from './(snippets)/go.txt';
|
||||
import SnippetDart from './(snippets)/dart.txt';
|
||||
import SnippetKotlin from './(snippets)/kotlin.txt';
|
||||
import SnippetJava from './(snippets)/java.txt';
|
||||
import SnippetSwift from './(snippets)/swift.txt';
|
||||
import SnippetCsharp from './(snippets)/csharp.txt';
|
||||
|
||||
const codeTopic = [
|
||||
{
|
||||
language: 'server-nodejs',
|
||||
platform: 'Web',
|
||||
content: `import { Client } from 'node-appwrite';
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
export default async ({ req, res, log, error }) => {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// const client = new Client()
|
||||
// .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
|
||||
// .setKey(req.headers['x-appwrite-key']);
|
||||
|
||||
// You can log messages to the console
|
||||
log('Hello, Logs!');
|
||||
|
||||
// If something goes wrong, log an error
|
||||
error('Hello, Errors!');
|
||||
|
||||
// The 'req' object contains the request data
|
||||
if (req.method === 'GET') {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return res.text('Hello, World!');
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return res.json({
|
||||
motto: 'Build like a team of hundreds_',
|
||||
learn: 'https://appwrite.io/docs',
|
||||
connect: 'https://appwrite.io/discord',
|
||||
getInspired: 'https://builtwith.appwrite.io',
|
||||
});
|
||||
};
|
||||
`
|
||||
content: SnippetNodejs
|
||||
},
|
||||
{
|
||||
language: 'php',
|
||||
platform: 'PHP',
|
||||
content: `require(__DIR__ . '/../vendor/autoload.php');
|
||||
|
||||
use Appwrite\Client;
|
||||
use Appwrite\Exception;
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
return function ($context) {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// $client = (new Client())
|
||||
// ->setProject(getenv(APPWRITE_FUNCTION_PROJECT_ID))
|
||||
// ->setKey($context->req->headers['x-appwrite-key']);
|
||||
|
||||
// You can log messages to the console
|
||||
$context->log('Hello, Logs!');
|
||||
|
||||
// If something goes wrong, log an error
|
||||
$context->error('Hello, Errors!');
|
||||
|
||||
// The 'req' object contains the request data
|
||||
if ($context->req->method === 'GET') {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return $context->res->text('Hello, World!');
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return $context->res->json([
|
||||
'motto' => 'Build like a team of hundreds_',
|
||||
'learn' => 'https://appwrite.io/docs',
|
||||
'connect' => 'https://appwrite.io/discord',
|
||||
'getInspired' => 'https://builtwith.appwrite.io',
|
||||
]);
|
||||
};
|
||||
`
|
||||
content: SnippetPhp
|
||||
},
|
||||
{
|
||||
language: 'python',
|
||||
platform: 'Python',
|
||||
content: `from appwrite.client import Client
|
||||
import os
|
||||
|
||||
|
||||
# This is your Appwrite function
|
||||
# It's executed each time we get a request
|
||||
def main(context):
|
||||
# Why not try the Appwrite SDK?
|
||||
#
|
||||
# Set project and set API key
|
||||
# client = (
|
||||
# Client()
|
||||
# .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"])
|
||||
# .set_key(context.req.headers["x-appwrite-key"])
|
||||
# )
|
||||
|
||||
# You can log messages to the console
|
||||
context.log("Hello, Logs!")
|
||||
|
||||
# If something goes wrong, log an error
|
||||
context.error("Hello, Errors!")
|
||||
|
||||
# The 'context.req' object contains the request data
|
||||
if context.req.method == "GET":
|
||||
# Send a response with the res object helpers
|
||||
# 'context.res.text()' dispatches a string back to the client
|
||||
return context.res.text("Hello, World!")
|
||||
|
||||
# 'context.res.json()' is a handy helper for sending JSON
|
||||
return context.res.json({
|
||||
"motto": "Build like a team of hundreds_",
|
||||
"learn": "https://appwrite.io/docs",
|
||||
"connect": "https://appwrite.io/discord",
|
||||
"getInspired": "https://builtwith.appwrite.io",
|
||||
})
|
||||
`
|
||||
content: SnippetPython
|
||||
},
|
||||
{
|
||||
language: 'ruby',
|
||||
content: `require "appwrite"
|
||||
|
||||
# This is your Appwrite function
|
||||
# It's executed each time we get a request
|
||||
def main(context)
|
||||
# Why not try the Appwrite SDK?
|
||||
#
|
||||
# Set project and set API key
|
||||
# client = Client.new
|
||||
# .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])
|
||||
# .set_key(context.req.headers['x-appwrite-key'])
|
||||
|
||||
# You can log messages to the console
|
||||
context.log("Hello, Logs!")
|
||||
|
||||
# If something goes wrong, log an error
|
||||
context.error("Hello, Errors!")
|
||||
|
||||
# The 'context.req' object contains the request data
|
||||
if (context.req.method == "GET")
|
||||
# Send a response with the res object helpers
|
||||
# 'context.res.text()' dispatches a string back to the client
|
||||
return context.res.text("Hello, World!")
|
||||
end
|
||||
|
||||
# 'context.res.json()' is a handy helper for sending JSON
|
||||
return context.res.json({
|
||||
"motto": "Build like a team of hundreds_",
|
||||
"learn": "https://appwrite.io/docs",
|
||||
"connect": "https://appwrite.io/discord",
|
||||
"getInspired": "https://builtwith.appwrite.io",
|
||||
})
|
||||
end
|
||||
`
|
||||
content: SnippetRuby
|
||||
},
|
||||
{
|
||||
language: 'deno',
|
||||
content: `import { Client } from "https://deno.land/x/appwrite@7.0.0/mod.ts";
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
export default ({ req, res, log, error }: any) => {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// const client = new Client()
|
||||
// .setProject(Deno.env.get("APPWRITE_FUNCTION_PROJECT_ID") || "")
|
||||
// .setKey(req.headers["x-appwrite-key"] || "");
|
||||
|
||||
// You can log messages to the console
|
||||
log("Hello, Logs!");
|
||||
|
||||
// If something goes wrong, log an error
|
||||
error("Hello, Errors!");
|
||||
|
||||
// The 'req' object contains the request data
|
||||
if (req.method === "GET") {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return res.text("Hello, World!");
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return res.json({
|
||||
motto: "Build like a team of hundreds_",
|
||||
learn: "https://appwrite.io/docs",
|
||||
connect: "https://appwrite.io/discord",
|
||||
getInspired: "https://builtwith.appwrite.io",
|
||||
});
|
||||
};
|
||||
`
|
||||
content: SnippetDeno
|
||||
},
|
||||
{
|
||||
language: 'go',
|
||||
content: `package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"github.com/appwrite/sdk-for-go/appwrite"
|
||||
"github.com/open-runtimes/types-for-go/v4/openruntimes"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Motto string 'json:"motto"'
|
||||
Learn string 'json:"learn"'
|
||||
Connect string 'json:"connect"'
|
||||
GetInspired string 'json:"getInspired"'
|
||||
}
|
||||
|
||||
func Main(Context openruntimes.Context) openruntimes.Response {
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request service
|
||||
var _ = appwrite.NewClient(
|
||||
appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")),
|
||||
appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]),
|
||||
)
|
||||
|
||||
// You can log messages to the console
|
||||
fmt.Println("Hello, Logs!")
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Error:", "Hello, Errors!")
|
||||
|
||||
// The 'Context.Req' object contains the request data
|
||||
if Context.Req.Method == "GET" {
|
||||
// Send a response with the Context.Res object helpers
|
||||
// 'Context.Res.Text()' dispatches a string back to the client
|
||||
return Context.Res.Text("Hello, World!")
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return Context.Res.Json(
|
||||
Response{
|
||||
Motto: "Build like a team of hundreds_",
|
||||
Learn: "https://appwrite.io/docs",
|
||||
Connect: "https://appwrite.io/discord",
|
||||
GetInspired: "https://builtwith.appwrite.io",
|
||||
})
|
||||
}
|
||||
`
|
||||
content: SnippetGo
|
||||
},
|
||||
{
|
||||
language: 'dart',
|
||||
content: `import 'dart:async';
|
||||
import 'package:dart_appwrite/dart_appwrite.dart';
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
Future main(final context) async {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// final client = Client()
|
||||
// .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])
|
||||
// .setKey(context.req.headers['x-appwrite-key']);
|
||||
|
||||
|
||||
// You can log messages to the console
|
||||
context.log('Hello, Logs!');
|
||||
|
||||
// If something goes wrong, log an error
|
||||
context.error('Hello, Errors!');
|
||||
|
||||
// The 'req' object contains the request data
|
||||
if (context.req.method == 'GET') {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return context.res.text('Hello, World!');
|
||||
}
|
||||
|
||||
// 'res.json()' is a handy helper for sending JSON
|
||||
return context.res.json({
|
||||
'motto': 'Build like a team of hundreds_',
|
||||
'learn': 'https://appwrite.io/docs',
|
||||
'connect': 'https://appwrite.io/discord',
|
||||
'getInspired': 'https://builtwith.appwrite.io',
|
||||
});
|
||||
}
|
||||
`
|
||||
content: SnippetDart
|
||||
},
|
||||
{
|
||||
language: 'kotlin',
|
||||
content: `package io.openruntimes.kotlin.src
|
||||
|
||||
import io.openruntimes.kotlin.RuntimeContext
|
||||
import io.openruntimes.kotlin.RuntimeOutput
|
||||
import io.appwrite.Client
|
||||
import java.util.HashMap
|
||||
|
||||
class Main {
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
fun main(context: RuntimeContext): RuntimeOutput {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// val client = Client()
|
||||
// .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID"))
|
||||
// .setKey(context.req.headers["x-appwrite-key"])
|
||||
|
||||
// You can log messages to the console
|
||||
context.log("Hello, Logs!")
|
||||
|
||||
// If something goes wrong, log an error
|
||||
context.error("Hello, Errors!")
|
||||
|
||||
// The 'context.req' object contains the request data
|
||||
if (context.req.method == "GET") {
|
||||
// Send a response with the res object helpers
|
||||
// 'context.res.text()' dispatches a string back to the client
|
||||
return context.res.text("Hello, World!")
|
||||
}
|
||||
|
||||
// 'context.res.json()' is a handy helper for sending JSON
|
||||
return context.res.json(mutableMapOf(
|
||||
"motto" to "Build like a team of hundreds_",
|
||||
"learn" to "https://appwrite.io/docs",
|
||||
"connect" to "https://appwrite.io/discord",
|
||||
"getInspired" to "https://builtwith.appwrite.io"
|
||||
))
|
||||
}
|
||||
}
|
||||
`
|
||||
content: SnippetKotlin
|
||||
},
|
||||
{
|
||||
language: 'java',
|
||||
content: `package io.openruntimes.java.src;
|
||||
|
||||
import io.openruntimes.java.RuntimeContext;
|
||||
import io.openruntimes.java.RuntimeOutput;
|
||||
import java.util.HashMap;
|
||||
import io.appwrite.Client;
|
||||
|
||||
public class Main {
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
public RuntimeOutput main(RuntimeContext context) throws Exception {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// Client client = new Client();
|
||||
// .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID"))
|
||||
// .setKey(context.getReq().getHeaders().get("x-appwrite-key"));
|
||||
|
||||
// You can log messages to the console
|
||||
context.log("Hello, Logs!");
|
||||
|
||||
// If something goes wrong, log an error
|
||||
context.error("Hello, Errors!");
|
||||
|
||||
// The 'context.getReq()' object contains the request data
|
||||
if (context.getReq().getMethod().equals("GET")) {
|
||||
// Send a response with the res object helpers
|
||||
// 'context.getRes().text()' dispatches a string back to the client
|
||||
return context.getRes().text("Hello, World!");
|
||||
}
|
||||
|
||||
Map json = new HashMap<>();
|
||||
json.put("motto", "Build like a team of hundreds_");
|
||||
json.put("learn", "https://appwrite.io/docs");
|
||||
json.put("connect", "https://appwrite.io/discord");
|
||||
json.put("getInspired", "https://builtwith.appwrite.io");
|
||||
|
||||
// 'context.getRes().json()' is a handy helper for sending JSON
|
||||
return context.getRes().json(json);
|
||||
}
|
||||
}
|
||||
`
|
||||
content: SnippetJava
|
||||
},
|
||||
{
|
||||
language: 'swift',
|
||||
content: `import Appwrite
|
||||
import AppwriteModels
|
||||
import Foundation
|
||||
|
||||
// This is your Appwrite function
|
||||
// It's executed each time we get a request
|
||||
func main(context: RuntimeContext) async throws -> RuntimeOutput {
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// let client = Client()
|
||||
// .setProject(ProcessInfo.processInfo.environment["APPWRITE_FUNCTION_PROJECT_ID"])
|
||||
// .setKey(context.req.headers["x-appwrite-key"] ?? "")
|
||||
|
||||
// You can log messages to the console
|
||||
context.log("Hello, Logs!")
|
||||
|
||||
// If something goes wrong, log an error
|
||||
context.error("Hello, Errors!")
|
||||
|
||||
// The 'context.req' object contains the request data
|
||||
if context.req.method == "GET" {
|
||||
// Send a response with the res object helpers
|
||||
// 'res.text()' dispatches a string back to the client
|
||||
return context.res.text("Hello, World!")
|
||||
}
|
||||
|
||||
// 'context.res.json()' is a handy helper for sending JSON
|
||||
return try context.res.json([
|
||||
"motto": "Build like a team of hundreds_",
|
||||
"learn": "https://appwrite.io/docs",
|
||||
"connect": "https://appwrite.io/discord",
|
||||
"getInspired": "https://builtwith.appwrite.io",
|
||||
])
|
||||
}
|
||||
`
|
||||
content: SnippetSwift
|
||||
},
|
||||
{
|
||||
language: 'csharp',
|
||||
content: `namespace DotNetRuntime;
|
||||
|
||||
using Appwrite;
|
||||
using Appwrite.Services;
|
||||
using Appwrite.Models;
|
||||
|
||||
public class Handler {
|
||||
|
||||
// This is your Appwrite function
|
||||
// It"s executed each time we get a request
|
||||
public async Task Main(RuntimeContext Context)
|
||||
{
|
||||
// Why not try the Appwrite SDK?
|
||||
//
|
||||
// Set project and set API key
|
||||
// var client = new Client()
|
||||
// .SetProject(Environment.GetEnvironmentVariable("APPWRITE_FUNCTION_PROJECT_ID"))
|
||||
// .SetKey(Context.Req.Headers["x-appwrite-key"]);
|
||||
|
||||
// You can log messages to the console
|
||||
Context.Log("Hello, Logs!");
|
||||
|
||||
// If something goes wrong, log an error
|
||||
Context.Error("Hello, Errors!");
|
||||
|
||||
// The 'Context.Req' object contains the request data
|
||||
if (Context.Req.Method == "GET") {
|
||||
// Send a response with the res object helpers
|
||||
// 'Context.Res.Text()' dispatches a string back to the client
|
||||
return Context.Res.Text("Hello, World!");
|
||||
}
|
||||
|
||||
// 'Context.Res.Json()' is a handy helper for sending JSON
|
||||
return Context.Res.Json(new Dictionary()
|
||||
{
|
||||
{ "motto", "Build like a team of hundreds_" },
|
||||
{ "learn", "https://appwrite.io/docs" },
|
||||
{ "connect", "https://appwrite.io/discord" },
|
||||
{ "getInspired", "https://builtwith.appwrite.io" },
|
||||
});
|
||||
}
|
||||
}
|
||||
`
|
||||
content: SnippetCsharp
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import Main from '$lib/layouts/Main.svelte';
|
||||
import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
|
||||
import { DEFAULT_HOST } from '$lib/utils/metadata';
|
||||
import { TITLE_SUFFIX } from '$routes/titles';
|
||||
import Templates from './(components)/Templates.svelte';
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
import DevelopLocally from './(components)/DevelopLocally.svelte';
|
||||
import DeploySeamlessly from './(components)/DeploySeamlessly.svelte';
|
||||
import Testimonials from './(components)/Testimonials.svelte';
|
||||
import RegionsMap from './(components)/RegionsMap.svelte';
|
||||
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
|
||||
import ProductCards from '$lib/components/product-pages/product-cards.svelte';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { FooterNav, MainFooter, PreFooter } from '$lib/components';
|
||||
import Main from '$lib/layouts/Main.svelte';
|
||||
import { DEFAULT_DESCRIPTION, DEFAULT_HOST } from '$lib/utils/metadata';
|
||||
import { DEFAULT_HOST } from '$lib/utils/metadata';
|
||||
import { TITLE_SUFFIX } from '$routes/titles';
|
||||
import Draft from './(components)/Draft.svelte';
|
||||
import Schedule from './(components)/Schedule.svelte';
|
||||
@@ -719,9 +719,6 @@ messaging.create_email(
|
||||
@media (max-width: 500px) {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
& a {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -238,10 +238,5 @@
|
||||
color: hsl(var(--web-color-primary));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-block-start: 1.5rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
static/images/bgs/building-blocks.webp
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 464 KiB |
|
After Width: | Height: | Size: 362 KiB |
|
After Width: | Height: | Size: 954 KiB |
|
After Width: | Height: | Size: 321 KiB |
|
After Width: | Height: | Size: 511 KiB |
|
After Width: | Height: | Size: 401 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
BIN
static/images/blog/appwrite-homepage-redesign/cover-image.png
Normal file
|
After Width: | Height: | Size: 265 KiB |
|
After Width: | Height: | Size: 379 KiB |
BIN
static/images/blog/appwrite-homepage-redesign/new-homepage.png
Normal file
|
After Width: | Height: | Size: 395 KiB |
BIN
static/images/blog/appwrite-homepage-redesign/old-homepage.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
static/images/blog/appwrite-homepage-redesign/scene.gif
Normal file
|
After Width: | Height: | Size: 668 KiB |