diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..4452e03
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "editor.definitionLinkOpensInPeek": false
+}
diff --git a/frontend/.prettierignore b/frontend/.prettierignore
new file mode 100644
index 0000000..7d74fe2
--- /dev/null
+++ b/frontend/.prettierignore
@@ -0,0 +1,9 @@
+# Package Managers
+package-lock.json
+pnpm-lock.yaml
+yarn.lock
+bun.lock
+bun.lockb
+
+# Miscellaneous
+/static/
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
index 5ad9d2e..ef82cc3 100644
--- a/frontend/.prettierrc
+++ b/frontend/.prettierrc
@@ -1,15 +1,15 @@
{
- "useTabs": true,
- "singleQuote": false,
- "trailingComma": "none",
- "printWidth": 100,
- "plugins": ["prettier-plugin-svelte"],
- "overrides": [
- {
- "files": "*.svelte",
- "options": {
- "parser": "svelte"
- }
- }
- ]
+ "useTabs": true,
+ "singleQuote": false,
+ "trailingComma": "none",
+ "printWidth": 100,
+ "plugins": ["prettier-plugin-svelte"],
+ "overrides": [
+ {
+ "files": "*.svelte",
+ "options": {
+ "parser": "svelte"
+ }
+ }
+ ]
}
diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json
index 5449017..11efe25 100644
--- a/frontend/.vscode/extensions.json
+++ b/frontend/.vscode/extensions.json
@@ -1,6 +1,3 @@
{
- "recommendations": [
- "svelte.svelte-vscode",
- "bradlc.vscode-tailwindcss"
- ]
+ "recommendations": ["svelte.svelte-vscode", "bradlc.vscode-tailwindcss", "esbenp.prettier-vscode"]
}
diff --git a/frontend/bun.lock b/frontend/bun.lock
index 37ee54a..e1322e8 100644
--- a/frontend/bun.lock
+++ b/frontend/bun.lock
@@ -25,6 +25,9 @@
"clsx": "^2.1.1",
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-svelte": "^8.6.0",
+ "prettier": "^3.8.3",
+ "prettier-plugin-svelte": "^4.1.0",
+ "prettier-plugin-tailwindcss": "^0.8.0",
"runed": "^0.37.1",
"shadcn-svelte": "^1.2.7",
"svelte": "^5.54.0",
@@ -423,6 +426,12 @@
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
+ "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="],
+
+ "prettier-plugin-svelte": ["prettier-plugin-svelte@4.1.1", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^5.0.0" } }, "sha512-wXvbXMjSvb4C9ENWTHXyd+ihakKCsJ6rJhLP6/8HFNj4GkZr48jqL9PoKsl2sk7SyCZRTnJ7O2TTowUpOxP/KA=="],
+
+ "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.8.1", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw=="],
+
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
diff --git a/frontend/package.json b/frontend/package.json
index fd052ff..892700e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,7 +10,9 @@
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
- "stripe-dev": "stripe listen --forward-to localhost:5173/api/webhook/stripe"
+ "stripe-dev": "stripe listen --forward-to localhost:5173/api/webhook/stripe",
+ "lint": "prettier --check .",
+ "format": "prettier --write ."
},
"devDependencies": {
"@fontsource-variable/inter": "^5.2.8",
@@ -24,6 +26,9 @@
"clsx": "^2.1.1",
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-svelte": "^8.6.0",
+ "prettier": "^3.8.3",
+ "prettier-plugin-svelte": "^4.1.0",
+ "prettier-plugin-tailwindcss": "^0.8.0",
"runed": "^0.37.1",
"shadcn-svelte": "^1.2.7",
"svelte": "^5.54.0",
diff --git a/frontend/prettier.config.js b/frontend/prettier.config.js
new file mode 100644
index 0000000..5dc395d
--- /dev/null
+++ b/frontend/prettier.config.js
@@ -0,0 +1,13 @@
+/** @type {import("prettier").Config} */
+const config = {
+ useTabs: true,
+ tabWidth: 4,
+ singleQuote: true,
+ trailingComma: "none",
+ printWidth: 100,
+ plugins: ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
+ overrides: [{ files: "*.svelte", options: { parser: "svelte" } }],
+ tailwindStylesheet: "./src/routes/layout.css"
+};
+
+export default config;
diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts
index 1f16ac4..8eb8b69 100644
--- a/frontend/src/app.d.ts
+++ b/frontend/src/app.d.ts
@@ -2,7 +2,7 @@ import type { UserModel } from "@/utils";
import type PocketBase from "pocketbase";
type AuthModel = {
- [key: string]: any;
+ [key: string]: any;
} | null;
// See https://svelte.dev/docs/kit/types#app.d.ts
@@ -11,8 +11,8 @@ declare global {
namespace App {
// interface Error {}
interface Locals {
- pb: PocketBase,
- user?: UserModel | undefined
+ pb: PocketBase;
+ user?: UserModel | undefined;
}
// interface PageData {}
// interface PageState {}
diff --git a/frontend/src/app.html b/frontend/src/app.html
index 0e9cc73..534acd7 100644
--- a/frontend/src/app.html
+++ b/frontend/src/app.html
@@ -3,9 +3,12 @@
-
-
-
+
+
+
%sveltekit.head%
diff --git a/frontend/src/cal.css b/frontend/src/cal.css
index 384df77..e3b2150 100644
--- a/frontend/src/cal.css
+++ b/frontend/src/cal.css
@@ -4,142 +4,142 @@
@custom-variant dark (&:is(.dark *));
:root {
- --background: oklch(18.22% 0.00002 271.152);
- --foreground: oklch(0.24 0 0);;
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --destructive-foreground: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --radius: 0.625rem;
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
+ --background: oklch(18.22% 0.00002 271.152);
+ --foreground: oklch(0.24 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
}
.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.145 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.145 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.985 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.396 0.141 25.723);
- --destructive-foreground: oklch(0.637 0.237 25.331);
- --border: oklch(0.269 0 0);
- --input: oklch(0.269 0 0);
- --ring: oklch(0.439 0 0);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(0.269 0 0);
- --sidebar-ring: oklch(0.439 0 0);
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
- --font-sans: "Geist", "Geist Fallback";
- --font-mono: "Geist Mono", "Geist Mono Fallback";
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-destructive-foreground: var(--destructive-foreground);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
+ --font-sans: "Geist", "Geist Fallback";
+ --font-mono: "Geist Mono", "Geist Mono Fallback";
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
}
@layer utilities {
- ::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- }
+ ::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+ }
- ::-webkit-scrollbar-track {
- background: #0a0a0a;
- }
+ ::-webkit-scrollbar-track {
+ background: #0a0a0a;
+ }
- ::-webkit-scrollbar-thumb {
- background: #333;
- border-radius: 4px;
- }
+ ::-webkit-scrollbar-thumb {
+ background: #333;
+ border-radius: 4px;
+ }
- ::-webkit-scrollbar-thumb:hover {
- background: #444;
- }
+ ::-webkit-scrollbar-thumb:hover {
+ background: #444;
+ }
}
diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts
index db50bd4..ff4ba70 100644
--- a/frontend/src/hooks.server.ts
+++ b/frontend/src/hooks.server.ts
@@ -4,30 +4,31 @@ import type { UserModel } from "@/utils";
config();
-export async function handle ({ event, resolve }) {
- event.locals.pb = new PocketBase(process.env["PB_URL"]);
- // event.locals.pb.authStore.save(process.env.POCKETBASE_TOKEN!, null);
- event.locals.pb.authStore.clear();
-
- const authCookie = event.cookies.get("pb_auth");
-
- if (authCookie) {
- try {
- event.locals.pb.authStore.loadFromCookie(authCookie);
-
- event.locals.pb.authStore.isValid && await event.locals.pb.collection('users').authRefresh();
-
- if (event.locals.pb.authStore.isValid) {
- event.locals.user = structuredClone(event.locals.pb.authStore.record) as UserModel;
- } else {
- event.locals.user = undefined;
- }
- } catch (_) {
- event.locals.pb.authStore.clear();
- }
- }
-
- const response = await resolve(event);
-
- return response;
-}
\ No newline at end of file
+export async function handle({ event, resolve }) {
+ event.locals.pb = new PocketBase(process.env["PB_URL"]);
+ // event.locals.pb.authStore.save(process.env.POCKETBASE_TOKEN!, null);
+ event.locals.pb.authStore.clear();
+
+ const authCookie = event.cookies.get("pb_auth");
+
+ if (authCookie) {
+ try {
+ event.locals.pb.authStore.loadFromCookie(authCookie);
+
+ event.locals.pb.authStore.isValid &&
+ (await event.locals.pb.collection("users").authRefresh());
+
+ if (event.locals.pb.authStore.isValid) {
+ event.locals.user = structuredClone(event.locals.pb.authStore.record) as UserModel;
+ } else {
+ event.locals.user = undefined;
+ }
+ } catch (_) {
+ event.locals.pb.authStore.clear();
+ }
+ }
+
+ const response = await resolve(event);
+
+ return response;
+}
diff --git a/frontend/src/lib/ACalToAdd.svelte b/frontend/src/lib/ACalToAdd.svelte
index 9d3bdcf..41fb0f3 100644
--- a/frontend/src/lib/ACalToAdd.svelte
+++ b/frontend/src/lib/ACalToAdd.svelte
@@ -1,28 +1,31 @@
-
-
addToCalsToAdd(event, calendar.id)}
- />
-
-
- {calendar.name}
-
-
\ No newline at end of file
+
+
addToCalsToAdd(event, calendar.id)}
+ />
+
+
+ {calendar.name}
+
+
diff --git a/frontend/src/lib/AnIncludedCalendar.svelte b/frontend/src/lib/AnIncludedCalendar.svelte
index b3e794c..18e2701 100644
--- a/frontend/src/lib/AnIncludedCalendar.svelte
+++ b/frontend/src/lib/AnIncludedCalendar.svelte
@@ -1,75 +1,88 @@
{#if actualCalendar}
-
- {#if actualCalendar.logo}
-
-
-
- {:else}
-
-
-
- {/if}
-
-
-
-
{actualCalendar.name}
- {#if !actualCalendar.filters.hideUnpublished}
-
- •
-
-
-
-
-
- This calendar can expose unpublished events. To change this, edit the filter settings of this calendar.
-
-
-
- {/if}
- {#if actualCalendar.passwordEnabled}
-
- •
-
-
-
-
-
- This calendar is password protected, but wont require a password in this image feed. If you dont want this calendar exposed without a password block, remove this calendar immediently!
-
-
-
- {/if}
-
-
- {actualCalendar.visits} visits
-
-
+
+ {#if actualCalendar.logo}
+
+
+
+ {:else}
+
+
+
+ {/if}
-
removeAdditionalCalendar(calendarId)}>
-
-{/if}
\ No newline at end of file
+
+
+
{actualCalendar.name}
+ {#if !actualCalendar.filters.hideUnpublished}
+
+ •
+
+
+
+
+
+ This calendar can expose unpublished events. To change this, edit the filter
+ settings of this calendar.
+
+
+
+ {/if}
+ {#if actualCalendar.passwordEnabled}
+
+ •
+
+
+
+
+
+ This calendar is password protected, but wont require a password in this image
+ feed. If you dont want this calendar exposed without a password block, remove this
+ calendar immediently!
+
+
+
+ {/if}
+
+
+ {actualCalendar.visits} visits
+
+
+
+
removeAdditionalCalendar(calendarId)}>
+
+{/if}
diff --git a/frontend/src/lib/Calendar.svelte b/frontend/src/lib/Calendar.svelte
index 17fddf0..25915aa 100644
--- a/frontend/src/lib/Calendar.svelte
+++ b/frontend/src/lib/Calendar.svelte
@@ -1,74 +1,110 @@
{#if calendarCustomizations.viewType === "month"}
-
+
{:else if calendarCustomizations.viewType === "week"}
-
+
{:else}
-
-{/if}
\ No newline at end of file
+
+{/if}
diff --git a/frontend/src/lib/Day.svelte b/frontend/src/lib/Day.svelte
index ef0e466..763ac11 100644
--- a/frontend/src/lib/Day.svelte
+++ b/frontend/src/lib/Day.svelte
@@ -1,36 +1,55 @@
-
-
- {#if dayNumber === 1}
- Today
- {:else if dayNumber === 2}
- Tomorrow
- {:else if dayNumber === 3}
- Day after Tomorrow
- {/if}
-
-
{LONGDAYTOSTRING[day.dayOfWeek]}, {MONTHTOSTRING[day.month]} {day.day}
-
+
+
+ {#if dayNumber === 1}
+ Today
+ {:else if dayNumber === 2}
+ Tomorrow
+ {:else if dayNumber === 3}
+ Day after Tomorrow
+ {/if}
+
+
+ {LONGDAYTOSTRING[day.dayOfWeek]}, {MONTHTOSTRING[day.month]}
+ {day.day}
+
+
-
- {#each events as event (`eventListDat${day.toString()}${event.id}`)}
- {#if !(!event.featured && filters.onlyShowFeatured) && !(!event.visibleInChurchCenter && filters.hideUnpublished)}
- {#if dateRangeOverlaps(day.toInstant().epochMilliseconds, nextDay.toInstant().epochMilliseconds, (new Date(event.startTime)).valueOf(), (new Date(event.endTime)).valueOf())}
-
- {/if}
- {/if}
- {/each}
-
-
\ No newline at end of file
+
+ {#each events as event (`eventListDat${day.toString()}${event.id}`)}
+ {#if !(!event.featured && filters.onlyShowFeatured) && !(!event.visibleInChurchCenter && filters.hideUnpublished)}
+ {#if dateRangeOverlaps(day.toInstant().epochMilliseconds, nextDay.toInstant().epochMilliseconds, new Date(event.startTime).valueOf(), new Date(event.endTime).valueOf())}
+
+ {/if}
+ {/if}
+ {/each}
+
+
diff --git a/frontend/src/lib/DynamicFeedAvatar.svelte b/frontend/src/lib/DynamicFeedAvatar.svelte
index d6a035f..e0ddd94 100644
--- a/frontend/src/lib/DynamicFeedAvatar.svelte
+++ b/frontend/src/lib/DynamicFeedAvatar.svelte
@@ -1,9 +1,9 @@
-
-
\ No newline at end of file
+
+
diff --git a/frontend/src/lib/Event.svelte b/frontend/src/lib/Event.svelte
index 7d71b4b..e887b43 100644
--- a/frontend/src/lib/Event.svelte
+++ b/frontend/src/lib/Event.svelte
@@ -1,103 +1,173 @@
-
-
-
{event.name}
-
{#if EVENT_DAY_NUMBER !== 1}{MONTHTOSTRING[start.month]} {start.day}, {/if}
-
-
- {#if event.description && calendarCustomizations.showDescription}
-
{event.description}
- {/if}
-
-
-
- {#if MULTI_DAY_EVENT}
- {MONTHTOSTRING[start.month]} {start.day}, - {MONTHTOSTRING[end.month]} {end.day},
- {:else}
- - {#if hours === 24 && start.hour === 0}{MONTHTOSTRING[end.month]} {end.day}, {/if}
- {/if}
-
-
- {#if MULTI_DAY_EVENT}
-
-
- Multi-day event - Day {EVENT_DAY_NUMBER}
-
- {/if}
-
-
-
- {#if event.times && event.times.length > 1}
-
-
-
-
Time Schedule:
-
- {#each event.times as time, index (`anEventTime${time.name}${event.id}`)}
- {#if index+1 < event.times.length}, {/if}
- {/each}
-
-
-
- {/if}
-
- {#if event.location && calendarCustomizations.showLocation}
-
-
-
- Location:
- {#if calendarCustomizations.onlyShowLocationTitle}{event.location.split(" - ")[0]}{:else}{event.location}{/if}
-
-
- {/if}
-
- {#if event.expand.tags}
-
- {#each event.expand.tags as tag (`taglist${tag.tag_id}${event.id}`)}
-
-
- {tag.name}
-
-
- {/each}
-
- {/if}
-
\ No newline at end of file
+
+
+
+ {event.name}
+
+
+ {#if EVENT_DAY_NUMBER !== 1}{MONTHTOSTRING[start.month]} {start.day},
+ {/if}
+
+
+
+
+ {#if event.description && calendarCustomizations.showDescription}
+
+ {event.description}
+
+ {/if}
+
+
+
+ {#if MULTI_DAY_EVENT}
+ {MONTHTOSTRING[start.month]}
+ {start.day}, - {MONTHTOSTRING[
+ end.month
+ ]}
+ {end.day},
+ {:else}
+ - {#if hours === 24 && start.hour === 0}{MONTHTOSTRING[
+ end.month
+ ]}
+ {end.day},
+ {/if}
+
+ {/if}
+
+
+ {#if MULTI_DAY_EVENT}
+
+
+ Multi-day event - Day {EVENT_DAY_NUMBER}
+
+ {/if}
+
+
+
+ {#if event.times && event.times.length > 1}
+
+
+
+
Time Schedule:
+
+ {#each event.times as time, index (`anEventTime${time.name}${event.id}`)}
+ {#if index + 1 < event.times.length}, {/if}
+ {/each}
+
+
+
+ {/if}
+
+ {#if event.location && calendarCustomizations.showLocation}
+
+
+
+ Location:
+ {#if calendarCustomizations.onlyShowLocationTitle}{event.location.split(
+ " - "
+ )[0]}{:else}{event.location}{/if}
+
+
+ {/if}
+
+ {#if event.expand.tags}
+
+ {#each event.expand.tags as tag (`taglist${tag.tag_id}${event.id}`)}
+
+
+ {tag.name}
+
+
+ {/each}
+
+ {/if}
+
diff --git a/frontend/src/lib/EventResources.svelte b/frontend/src/lib/EventResources.svelte
index f0a2f30..e84ef31 100644
--- a/frontend/src/lib/EventResources.svelte
+++ b/frontend/src/lib/EventResources.svelte
@@ -1,48 +1,69 @@
{#if rooms.length > 0 && showRooms}
-
-
-
- Room{#if rooms.length > 1}s{/if}:
-
- {#each rooms as room, index}
- {#if room.path_name}{room.path_name}\\{/if} {room.name}{#if index+1 < rooms.length}, {/if}
- {/each}
-
-
-
+
+
+
+ Room{#if rooms.length > 1}s{/if}:
+
+ {#each rooms as room, index}
+ {#if room.path_name}{room.path_name}\\{/if}
+ {room.name}{#if index + 1 < rooms.length},
+ {/if}
+ {/each}
+
+
+
{/if}
{#if res.length > 0 && showResources}
-
-
-
- Resource{#if res.length > 1}s{/if}:
-
- {#each res as resource, index}
- {resource.name}{#if resource.path_name && showResourcePathname}({resource.path_name.trimEnd()}) {/if}{#if index+1 < res.length}, {/if}
- {/each}
-
-
-
-{/if}
\ No newline at end of file
+
+
+
+ Resource{#if res.length > 1}s{/if}:
+
+ {#each res as resource, index}
+ {resource.name}{#if resource.path_name && showResourcePathname}({resource.path_name.trimEnd()}) {/if}{#if index + 1 < res.length},
+ {/if}
+ {/each}
+
+
+
+{/if}
diff --git a/frontend/src/lib/EventTimes.svelte b/frontend/src/lib/EventTimes.svelte
index affb51e..70920bf 100644
--- a/frontend/src/lib/EventTimes.svelte
+++ b/frontend/src/lib/EventTimes.svelte
@@ -1,13 +1,32 @@
-{time.name}: {#if multiDayEvent && start.day !== today.day}{MONTHTOSTRING[start.month]} {start.day}, {/if} - {#if multiDayEvent && end.day !== today.day}{MONTHTOSTRING[end.month]} {end.day}, {/if}
+{time.name}: {#if multiDayEvent && start.day !== today.day}{MONTHTOSTRING[start.month]} {start.day},
+{/if}
+ - {#if multiDayEvent && end.day !== today.day}{MONTHTOSTRING[
+ end.month
+ ]}
+ {end.day},
+{/if}
+
diff --git a/frontend/src/lib/EverythingSearch.svelte b/frontend/src/lib/EverythingSearch.svelte
index 4e0d27f..f84c81c 100644
--- a/frontend/src/lib/EverythingSearch.svelte
+++ b/frontend/src/lib/EverythingSearch.svelte
@@ -1,171 +1,192 @@
-
-
-
- ⌘
- +
- /
-
-
-
everythingInputFocused=true}
- onfocusout={() => setTimeout(() => everythingInputFocused=false, 200)}
- oninput={searchInputChanged}
- placeholder="Search calendars, image feeds..."
- class="pl-9 pr-9 bg-background"
- />
-
- {#if everythingInputFocused && filteredTerms.length > 0}
-
- {/if}
+
+
+
+ ⌘
+ +
+ /
+
+
+
(everythingInputFocused = true)}
+ onfocusout={() => setTimeout(() => (everythingInputFocused = false), 200)}
+ oninput={searchInputChanged}
+ placeholder="Search calendars, image feeds..."
+ class="pl-9 pr-9 bg-background"
+ />
+
+ {#if everythingInputFocused && filteredTerms.length > 0}
+
+ {/if}
\ No newline at end of file
+ @media (max-width: 500px) {
+ .everythingSearcher {
+ min-width: calc(100vw - 20px) !important;
+ left: -58px;
+ }
+ }
+
diff --git a/frontend/src/lib/IncludedCalendars.svelte b/frontend/src/lib/IncludedCalendars.svelte
index 54f0d8c..9d58c77 100644
--- a/frontend/src/lib/IncludedCalendars.svelte
+++ b/frontend/src/lib/IncludedCalendars.svelte
@@ -1,150 +1,172 @@
-
- {#if includedCalendars.length > 0}
- {#each includedCalendars as calendar (`includedcals${calendar}`)}
-
- {/each}
-
- includeACalendarPopup = true}>Include Calendar
-
- {:else}
-
-
-
-
-
- No Included Calendars
-
- You haven't included a calendar yet. If you haven't created any calendars, create one and then include it here.
-
-
-
-
- includeACalendarPopup = true}>Include Calendar
- Create A Calendar
-
-
-
- {/if}
+ {#if includedCalendars.length > 0}
+ {#each includedCalendars as calendar (`includedcals${calendar}`)}
+
+ {/each}
+
+ (includeACalendarPopup = true)}
+ >Include Calendar
+
+ {:else}
+
+
+
+
+
+ No Included Calendars
+
+ You haven't included a calendar yet. If you haven't created any calendars, create one and
+ then include it here.
+
+
+
+
+ (includeACalendarPopup = true)}>Include Calendar
+ Create A Calendar
+
+
+
+ {/if}
-
-
- Incude Calendar
- KEEP IN MIND that a password protected calendar will be public and will not require a password. Also, any calendar that has access to UNPUBLISHED events also will be public.
-
-
-
- {#if includedCalendars.length === calendars.length}
-
-
-
-
-
- No {#if calendars.length !== 0}Extra{/if} Calendars
-
- {#if calendars.length === 0}
- You haven't created any calendars yet, create one and then include it here.
- {:else}
- You already included all you current calendars, create a new one and then include it here.
- {/if}
-
-
-
-
- Create A Calendar
-
-
-
- {:else}
- {#each calendars as calendar (`acalendartoadd${calendar.id}`)}
- {#if !includedCalendars.includes(calendar.id)}
-
- {/if}
- {/each}
- {/if}
-
-
- Add
-
-
-
\ No newline at end of file
+
+
+ Incude Calendar
+ KEEP IN MIND that a password protected calendar will be public and will not require a
+ password. Also, any calendar that has access to UNPUBLISHED events also will be public.
+
+
+
+ {#if includedCalendars.length === calendars.length}
+
+
+
+
+
+ No {#if calendars.length !== 0}Extra{/if} Calendars
+
+ {#if calendars.length === 0}
+ You haven't created any calendars yet, create one and then include it here.
+ {:else}
+ You already included all you current calendars, create a new one and then include it
+ here.
+ {/if}
+
+
+
+
+ Create A Calendar
+
+
+
+ {:else}
+ {#each calendars as calendar (`acalendartoadd${calendar.id}`)}
+ {#if !includedCalendars.includes(calendar.id)}
+
+ {/if}
+ {/each}
+ {/if}
+
+
+ Add
+
+
+
diff --git a/frontend/src/lib/ModeToggler.svelte b/frontend/src/lib/ModeToggler.svelte
index 3867037..90c9b26 100644
--- a/frontend/src/lib/ModeToggler.svelte
+++ b/frontend/src/lib/ModeToggler.svelte
@@ -1,11 +1,15 @@
-
-
- Toggle theme
-
\ No newline at end of file
+
+
+ Toggle theme
+
diff --git a/frontend/src/lib/NoCalendarAvatar.svelte b/frontend/src/lib/NoCalendarAvatar.svelte
index 1bc79e1..13bae0d 100644
--- a/frontend/src/lib/NoCalendarAvatar.svelte
+++ b/frontend/src/lib/NoCalendarAvatar.svelte
@@ -1,9 +1,9 @@
-
-
\ No newline at end of file
+
+
diff --git a/frontend/src/lib/NoEventListAvatar.svelte b/frontend/src/lib/NoEventListAvatar.svelte
index 8d46faf..fca3a02 100644
--- a/frontend/src/lib/NoEventListAvatar.svelte
+++ b/frontend/src/lib/NoEventListAvatar.svelte
@@ -1,9 +1,9 @@
-
-
\ No newline at end of file
+
+
diff --git a/frontend/src/lib/NoImageFeedAvatar.svelte b/frontend/src/lib/NoImageFeedAvatar.svelte
index 6d4329e..ea1516d 100644
--- a/frontend/src/lib/NoImageFeedAvatar.svelte
+++ b/frontend/src/lib/NoImageFeedAvatar.svelte
@@ -1,9 +1,9 @@
-
-
\ No newline at end of file
+
+
diff --git a/frontend/src/lib/PrettyDate.svelte b/frontend/src/lib/PrettyDate.svelte
index 4552080..7384675 100644
--- a/frontend/src/lib/PrettyDate.svelte
+++ b/frontend/src/lib/PrettyDate.svelte
@@ -1,9 +1,12 @@
-{MONTHTOSTRING[date.month]} {date.day} {date.year}
\ No newline at end of file
+{MONTHTOSTRING[date.month]}
+{date.day}
+{date.year}
+
diff --git a/frontend/src/lib/Time.svelte b/frontend/src/lib/Time.svelte
index d18578d..80d858f 100644
--- a/frontend/src/lib/Time.svelte
+++ b/frontend/src/lib/Time.svelte
@@ -1,29 +1,30 @@
-{hour}:{minute} {#if useAMPM}{#if isAM}AM{:else}PM{/if}{/if}
\ No newline at end of file
+{hour}:{minute}
+{#if useAMPM}{#if isAM}AM{:else}PM{/if}{/if}
diff --git a/frontend/src/lib/TimeZoneSelector.svelte b/frontend/src/lib/TimeZoneSelector.svelte
index d3c9c2d..7766df8 100644
--- a/frontend/src/lib/TimeZoneSelector.svelte
+++ b/frontend/src/lib/TimeZoneSelector.svelte
@@ -1,50 +1,49 @@
-
+
-
- {triggerContent}
-
-
-
- Timezones
- {#each timezones as timezone (timezone)}
-
- {timezone}
-
- {/each}
-
-
-
\ No newline at end of file
+
+ {triggerContent}
+
+
+
+ Timezones
+ {#each timezones as timezone (timezone)}
+
+ {timezone}
+
+ {/each}
+
+
+
diff --git a/frontend/src/lib/cal.utils.ts b/frontend/src/lib/cal.utils.ts
index 37ff1d2..f8c167f 100644
--- a/frontend/src/lib/cal.utils.ts
+++ b/frontend/src/lib/cal.utils.ts
@@ -1,15 +1,15 @@
import type { RecordModel } from "pocketbase";
export type CalendarCustomizations = {
- viewType: "3day" | "week" | "month",
- useAMPM: boolean,
- showResourcePathname: boolean,
- onlyShowLocationTitle: boolean,
- showLocation: boolean,
- showResources: boolean,
- showRooms: boolean,
- showDescription: boolean
-}
+ viewType: "3day" | "week" | "month";
+ useAMPM: boolean;
+ showResourcePathname: boolean;
+ onlyShowLocationTitle: boolean;
+ showLocation: boolean;
+ showResources: boolean;
+ showRooms: boolean;
+ showDescription: boolean;
+};
export const defaultCalendarCustomizations: CalendarCustomizations = {
viewType: "3day",
@@ -20,19 +20,19 @@ export const defaultCalendarCustomizations: CalendarCustomizations = {
showResources: true,
showRooms: true,
showDescription: false
-}
+};
export type CalendarFilters = {
- onlyShowFeatured: boolean,
- hideUnpublished: boolean,
- resourceFilterType: "allow" | "block"
- allowResources: string[],
- blockResources: string[],
- enableResourceFiltering: boolean,
- tagFilterType: "allow" | "block",
- allowTags: string[],
- blockTags: string[],
- enableTagFiltering: boolean
+ onlyShowFeatured: boolean;
+ hideUnpublished: boolean;
+ resourceFilterType: "allow" | "block";
+ allowResources: string[];
+ blockResources: string[];
+ enableResourceFiltering: boolean;
+ tagFilterType: "allow" | "block";
+ allowTags: string[];
+ blockTags: string[];
+ enableTagFiltering: boolean;
};
export const defaultCalendarFilters: CalendarFilters = {
@@ -46,19 +46,20 @@ export const defaultCalendarFilters: CalendarFilters = {
allowTags: [],
blockTags: [],
enableTagFiltering: false
-}
+};
export interface CalendarDBModel extends RecordModel {
- name: string,
- password: string,
- passwordEnabled: boolean,
- owner: string,
- logo: string | File,
- visits: number,
- filters: CalendarFilters,
- description: string,
- passwordScreenMessage: string,
- displaySettings: CalendarCustomizations,
- created: string,
- updated: string
-}
\ No newline at end of file
+ publicId: string;
+ name: string;
+ password: string;
+ passwordEnabled: boolean;
+ owner: string;
+ logo: string | File;
+ visits: number;
+ filters: CalendarFilters;
+ description: string;
+ passwordScreenMessage: string;
+ displaySettings: CalendarCustomizations;
+ created: string;
+ updated: string;
+}
diff --git a/frontend/src/lib/calendar/monthView/MonthCalView.svelte b/frontend/src/lib/calendar/monthView/MonthCalView.svelte
index b8f41a9..5ce1fde 100644
--- a/frontend/src/lib/calendar/monthView/MonthCalView.svelte
+++ b/frontend/src/lib/calendar/monthView/MonthCalView.svelte
@@ -1,41 +1,52 @@
-
-
- {#each ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as day (`aweekday${day}`)}
-
- {day}
-
- {/each}
-
+
+
+ {#each ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as day (`aweekday${day}`)}
+
+ {day}
+
+ {/each}
+
-
- {#each days as day, index (`adayinmonth${index}`)}
-
- {/each}
-
-
-
\ No newline at end of file
+
+ {#each days as day, index (`adayinmonth${index}`)}
+
+ {/each}
+
+
+
diff --git a/frontend/src/lib/calendar/monthView/MonthCalViewDay.svelte b/frontend/src/lib/calendar/monthView/MonthCalViewDay.svelte
index 805cbd9..f0cab4c 100644
--- a/frontend/src/lib/calendar/monthView/MonthCalViewDay.svelte
+++ b/frontend/src/lib/calendar/monthView/MonthCalViewDay.svelte
@@ -1,56 +1,62 @@
-
- {day.day}
-
+
+ {day.day}
+
-
- {#each dayEvents as event (`aneventfortheday${event.id}`)}
-
- {/each}
-
-
\ No newline at end of file
+
+ {#each dayEvents as event (`aneventfortheday${event.id}`)}
+
+ {/each}
+
+
diff --git a/frontend/src/lib/calendar/monthView/MonthCalViewDayEvent.svelte b/frontend/src/lib/calendar/monthView/MonthCalViewDayEvent.svelte
index 80903c1..d7693df 100644
--- a/frontend/src/lib/calendar/monthView/MonthCalViewDayEvent.svelte
+++ b/frontend/src/lib/calendar/monthView/MonthCalViewDayEvent.svelte
@@ -1,29 +1,50 @@
-
- {event.name}
-
-
- {#if EVENT_DAY_NUMBER !== 1}
- Day {EVENT_DAY_NUMBER}
- {:else}
-
- {/if}
-
-
\ No newline at end of file
+
+ {event.name}
+
+
+ {#if EVENT_DAY_NUMBER !== 1}
+ Day {EVENT_DAY_NUMBER}
+ {:else}
+
+ {/if}
+
+
diff --git a/frontend/src/lib/calendar/weekView/WeekCalView.svelte b/frontend/src/lib/calendar/weekView/WeekCalView.svelte
index 3a51dfc..056681d 100644
--- a/frontend/src/lib/calendar/weekView/WeekCalView.svelte
+++ b/frontend/src/lib/calendar/weekView/WeekCalView.svelte
@@ -1,41 +1,52 @@
-
-
- {#each days as day (`aweekday${day.dayOfWeek}`)}
-
- {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][day.dayOfWeek]}
-
- {/each}
-
+
+
+ {#each days as day (`aweekday${day.dayOfWeek}`)}
+
+ {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][day.dayOfWeek]}
+
+ {/each}
+
-
- {#each days as day, index (`adayinmonth${index}`)}
-
- {/each}
-
-
-
\ No newline at end of file
+
+ {#each days as day, index (`adayinmonth${index}`)}
+
+ {/each}
+
+
+
diff --git a/frontend/src/lib/calendar/weekView/WeekCalViewDay.svelte b/frontend/src/lib/calendar/weekView/WeekCalViewDay.svelte
index 1144b93..f66090f 100644
--- a/frontend/src/lib/calendar/weekView/WeekCalViewDay.svelte
+++ b/frontend/src/lib/calendar/weekView/WeekCalViewDay.svelte
@@ -1,56 +1,62 @@
-
- {day.day}
-
+
+ {day.day}
+
-
- {#each dayEvents as event (`aneventfortheday${event.id}`)}
-
- {/each}
-
-
\ No newline at end of file
+
+ {#each dayEvents as event (`aneventfortheday${event.id}`)}
+
+ {/each}
+
+
diff --git a/frontend/src/lib/calendar/weekView/WeekCalViewDayEvent.svelte b/frontend/src/lib/calendar/weekView/WeekCalViewDayEvent.svelte
index 2d57d26..207054f 100644
--- a/frontend/src/lib/calendar/weekView/WeekCalViewDayEvent.svelte
+++ b/frontend/src/lib/calendar/weekView/WeekCalViewDayEvent.svelte
@@ -1,40 +1,76 @@
-
-
{event.name}
-
-
-
-
- {#if MULTI_DAY_EVENT}
- {MONTHTOSTRING[start.month]} {start.day}, - {MONTHTOSTRING[end.month]} {end.day},
- {:else}
- - {#if hours === 24 && start.hour === 0}{MONTHTOSTRING[end.month]} {end.day}, {/if}
- {/if}
-
+
+
+ {event.name}
+
+
- {#if MULTI_DAY_EVENT}
-
-
- Multi-day event - Day {EVENT_DAY_NUMBER}
-
- {/if}
-
\ No newline at end of file
+
+
+ {#if MULTI_DAY_EVENT}
+ {MONTHTOSTRING[start.month]}
+ {start.day}, - {MONTHTOSTRING[
+ end.month
+ ]}
+ {end.day},
+ {:else}
+ - {#if hours === 24 && start.hour === 0}{MONTHTOSTRING[
+ end.month
+ ]}
+ {end.day},
+ {/if}
+
+ {/if}
+
+
+ {#if MULTI_DAY_EVENT}
+
+
+ Multi-day event - Day {EVENT_DAY_NUMBER}
+
+ {/if}
+
diff --git a/frontend/src/lib/components/ui/avatar/index.ts b/frontend/src/lib/components/ui/avatar/index.ts
index d06457b..b08c780 100644
--- a/frontend/src/lib/components/ui/avatar/index.ts
+++ b/frontend/src/lib/components/ui/avatar/index.ts
@@ -9,5 +9,5 @@ export {
//
Root as Avatar,
Image as AvatarImage,
- Fallback as AvatarFallback,
+ Fallback as AvatarFallback
};
diff --git a/frontend/src/lib/components/ui/badge/badge.svelte b/frontend/src/lib/components/ui/badge/badge.svelte
index bfaa9c5..33233da 100644
--- a/frontend/src/lib/components/ui/badge/badge.svelte
+++ b/frontend/src/lib/components/ui/badge/badge.svelte
@@ -5,18 +5,17 @@
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-full border px-2 py-0.5 text-xs font-medium transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3",
variants: {
variant: {
- default:
- "bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent",
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent",
secondary:
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
destructive:
"bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white",
- outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
- },
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
+ }
},
defaultVariants: {
- variant: "default",
- },
+ variant: "default"
+ }
});
export type BadgeVariant = VariantProps["variant"];
diff --git a/frontend/src/lib/components/ui/button/button.svelte b/frontend/src/lib/components/ui/button/button.svelte
index 2105474..b9a587c 100644
--- a/frontend/src/lib/components/ui/button/button.svelte
+++ b/frontend/src/lib/components/ui/button/button.svelte
@@ -14,7 +14,7 @@
"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
- link: "text-primary underline-offset-4 hover:underline",
+ link: "text-primary underline-offset-4 hover:underline"
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
@@ -22,13 +22,13 @@
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
- "icon-lg": "size-10",
- },
+ "icon-lg": "size-10"
+ }
},
defaultVariants: {
variant: "default",
- size: "default",
- },
+ size: "default"
+ }
});
export type ButtonVariant = VariantProps["variant"];
diff --git a/frontend/src/lib/components/ui/button/index.ts b/frontend/src/lib/components/ui/button/index.ts
index fb585d7..068bfa2 100644
--- a/frontend/src/lib/components/ui/button/index.ts
+++ b/frontend/src/lib/components/ui/button/index.ts
@@ -2,7 +2,7 @@ import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
- buttonVariants,
+ buttonVariants
} from "./button.svelte";
export {
@@ -13,5 +13,5 @@ export {
buttonVariants,
type ButtonProps,
type ButtonSize,
- type ButtonVariant,
+ type ButtonVariant
};
diff --git a/frontend/src/lib/components/ui/calendar/calendar-caption.svelte b/frontend/src/lib/components/ui/calendar/calendar-caption.svelte
index 5c93037..d545234 100644
--- a/frontend/src/lib/components/ui/calendar/calendar-caption.svelte
+++ b/frontend/src/lib/components/ui/calendar/calendar-caption.svelte
@@ -14,7 +14,7 @@
month,
locale,
placeholder = $bindable(),
- monthIndex = 0,
+ monthIndex = 0
}: {
captionLayout: ComponentProps["captionLayout"];
months: ComponentProps["months"];
diff --git a/frontend/src/lib/components/ui/calendar/calendar.svelte b/frontend/src/lib/components/ui/calendar/calendar.svelte
index 29b6fff..0c8ee1c 100644
--- a/frontend/src/lib/components/ui/calendar/calendar.svelte
+++ b/frontend/src/lib/components/ui/calendar/calendar.svelte
@@ -97,7 +97,7 @@ get along, so we shut typescript up by casting `value` to `never`.
{#if day}
{@render day({
day: date,
- outsideMonth: !isEqualMonth(date, month.value),
+ outsideMonth: !isEqualMonth(date, month.value)
})}
{:else}
diff --git a/frontend/src/lib/components/ui/calendar/index.ts b/frontend/src/lib/components/ui/calendar/index.ts
index f3a16d2..e6f7726 100644
--- a/frontend/src/lib/components/ui/calendar/index.ts
+++ b/frontend/src/lib/components/ui/calendar/index.ts
@@ -36,5 +36,5 @@ export {
MonthSelect,
Caption,
//
- Root as Calendar,
+ Root as Calendar
};
diff --git a/frontend/src/lib/components/ui/card/card.svelte b/frontend/src/lib/components/ui/card/card.svelte
index 67b820b..2ccde86 100644
--- a/frontend/src/lib/components/ui/card/card.svelte
+++ b/frontend/src/lib/components/ui/card/card.svelte
@@ -26,4 +26,4 @@
div {
max-width: calc(100vw - 8px) !important;
}
-
\ No newline at end of file
+
diff --git a/frontend/src/lib/components/ui/card/index.ts b/frontend/src/lib/components/ui/card/index.ts
index 4d3fce4..10daffb 100644
--- a/frontend/src/lib/components/ui/card/index.ts
+++ b/frontend/src/lib/components/ui/card/index.ts
@@ -21,5 +21,5 @@ export {
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle,
- Action as CardAction,
+ Action as CardAction
};
diff --git a/frontend/src/lib/components/ui/carousel/carousel-content.svelte b/frontend/src/lib/components/ui/carousel/carousel-content.svelte
index 6b169be..e315003 100644
--- a/frontend/src/lib/components/ui/carousel/carousel-content.svelte
+++ b/frontend/src/lib/components/ui/carousel/carousel-content.svelte
@@ -22,9 +22,9 @@
container: "[data-embla-container]",
slides: "[data-embla-slide]",
...emblaCtx.options,
- axis: emblaCtx.orientation === "horizontal" ? "x" : "y",
+ axis: emblaCtx.orientation === "horizontal" ? "x" : "y"
},
- plugins: emblaCtx.plugins,
+ plugins: emblaCtx.plugins
}}
onemblaInit={emblaCtx.onInit}
>
diff --git a/frontend/src/lib/components/ui/carousel/carousel.svelte b/frontend/src/lib/components/ui/carousel/carousel.svelte
index 9f781e5..4f581d7 100644
--- a/frontend/src/lib/components/ui/carousel/carousel.svelte
+++ b/frontend/src/lib/components/ui/carousel/carousel.svelte
@@ -3,7 +3,7 @@
type CarouselAPI,
type CarouselProps,
type EmblaContext,
- setEmblaContext,
+ setEmblaContext
} from "./context.js";
import { cn, type WithElementRef } from "$lib/utils.js";
@@ -34,7 +34,7 @@
onInit,
scrollSnaps: [],
selectedIndex: 0,
- scrollTo,
+ scrollTo
});
setEmblaContext(carouselState);
diff --git a/frontend/src/lib/components/ui/carousel/context.ts b/frontend/src/lib/components/ui/carousel/context.ts
index a5fd74f..a02fff7 100644
--- a/frontend/src/lib/components/ui/carousel/context.ts
+++ b/frontend/src/lib/components/ui/carousel/context.ts
@@ -1,7 +1,7 @@
import type { WithElementRef } from "$lib/utils.js";
import type {
EmblaCarouselSvelteType,
- default as emblaCarouselSvelte,
+ default as emblaCarouselSvelte
} from "embla-carousel-svelte";
import { getContext, hasContext, setContext } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
diff --git a/frontend/src/lib/components/ui/carousel/index.ts b/frontend/src/lib/components/ui/carousel/index.ts
index 957fc74..9aaf431 100644
--- a/frontend/src/lib/components/ui/carousel/index.ts
+++ b/frontend/src/lib/components/ui/carousel/index.ts
@@ -15,5 +15,5 @@ export {
Content as CarouselContent,
Item as CarouselItem,
Previous as CarouselPrevious,
- Next as CarouselNext,
+ Next as CarouselNext
};
diff --git a/frontend/src/lib/components/ui/checkbox/index.ts b/frontend/src/lib/components/ui/checkbox/index.ts
index 6d92d94..5fba5a4 100644
--- a/frontend/src/lib/components/ui/checkbox/index.ts
+++ b/frontend/src/lib/components/ui/checkbox/index.ts
@@ -2,5 +2,5 @@ import Root from "./checkbox.svelte";
export {
Root,
//
- Root as Checkbox,
+ Root as Checkbox
};
diff --git a/frontend/src/lib/components/ui/collapsible/index.ts b/frontend/src/lib/components/ui/collapsible/index.ts
index 169b479..d5db2aa 100644
--- a/frontend/src/lib/components/ui/collapsible/index.ts
+++ b/frontend/src/lib/components/ui/collapsible/index.ts
@@ -9,5 +9,5 @@ export {
//
Root as Collapsible,
Content as CollapsibleContent,
- Trigger as CollapsibleTrigger,
+ Trigger as CollapsibleTrigger
};
diff --git a/frontend/src/lib/components/ui/dialog/index.ts b/frontend/src/lib/components/ui/dialog/index.ts
index dce1d9d..790315c 100644
--- a/frontend/src/lib/components/ui/dialog/index.ts
+++ b/frontend/src/lib/components/ui/dialog/index.ts
@@ -33,5 +33,5 @@ export {
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,
- Close as DialogClose,
+ Close as DialogClose
};
diff --git a/frontend/src/lib/components/ui/drawer/index.ts b/frontend/src/lib/components/ui/drawer/index.ts
index cfbdb8b..6dce95d 100644
--- a/frontend/src/lib/components/ui/drawer/index.ts
+++ b/frontend/src/lib/components/ui/drawer/index.ts
@@ -37,5 +37,5 @@ export {
Title as DrawerTitle,
Trigger as DrawerTrigger,
Portal as DrawerPortal,
- Close as DrawerClose,
+ Close as DrawerClose
};
diff --git a/frontend/src/lib/components/ui/dropdown-menu/index.ts b/frontend/src/lib/components/ui/dropdown-menu/index.ts
index 1cf9f70..342f314 100644
--- a/frontend/src/lib/components/ui/dropdown-menu/index.ts
+++ b/frontend/src/lib/components/ui/dropdown-menu/index.ts
@@ -45,5 +45,5 @@ export {
Sub,
SubContent,
SubTrigger,
- Trigger,
+ Trigger
};
diff --git a/frontend/src/lib/components/ui/empty/empty-media.svelte b/frontend/src/lib/components/ui/empty/empty-media.svelte
index 0b4e45d..323be1e 100644
--- a/frontend/src/lib/components/ui/empty/empty-media.svelte
+++ b/frontend/src/lib/components/ui/empty/empty-media.svelte
@@ -6,12 +6,12 @@
variants: {
variant: {
default: "bg-transparent",
- icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
- },
+ icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6"
+ }
},
defaultVariants: {
- variant: "default",
- },
+ variant: "default"
+ }
});
export type EmptyMediaVariant = VariantProps["variant"];
diff --git a/frontend/src/lib/components/ui/empty/index.ts b/frontend/src/lib/components/ui/empty/index.ts
index ae4c106..ee84b29 100644
--- a/frontend/src/lib/components/ui/empty/index.ts
+++ b/frontend/src/lib/components/ui/empty/index.ts
@@ -18,5 +18,5 @@ export {
Media as EmptyMedia,
Title as EmptyTitle,
Description as EmptyDescription,
- Content as EmptyContent,
+ Content as EmptyContent
};
diff --git a/frontend/src/lib/components/ui/field/field-separator.svelte b/frontend/src/lib/components/ui/field/field-separator.svelte
index 12bcb77..e06410a 100644
--- a/frontend/src/lib/components/ui/field/field-separator.svelte
+++ b/frontend/src/lib/components/ui/field/field-separator.svelte
@@ -20,10 +20,7 @@
bind:this={ref}
data-slot="field-separator"
data-content={hasContent}
- class={cn(
- "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
- className
- )}
+ class={cn("relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", className)}
{...restProps}
>
diff --git a/frontend/src/lib/components/ui/field/field.svelte b/frontend/src/lib/components/ui/field/field.svelte
index 3284203..0daefca 100644
--- a/frontend/src/lib/components/ui/field/field.svelte
+++ b/frontend/src/lib/components/ui/field/field.svelte
@@ -9,18 +9,18 @@
horizontal: [
"flex-row items-center",
"[&>[data-slot=field-label]]:flex-auto",
- "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
+ "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
],
responsive: [
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
- "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
- ],
- },
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
+ ]
+ }
},
defaultVariants: {
- orientation: "vertical",
- },
+ orientation: "vertical"
+ }
});
export type FieldOrientation = VariantProps["orientation"];
diff --git a/frontend/src/lib/components/ui/field/index.ts b/frontend/src/lib/components/ui/field/index.ts
index a644a95..39fbe1e 100644
--- a/frontend/src/lib/components/ui/field/index.ts
+++ b/frontend/src/lib/components/ui/field/index.ts
@@ -29,5 +29,5 @@ export {
Title as FieldTitle,
Description as FieldDescription,
Separator as FieldSeparator,
- Error as FieldError,
+ Error as FieldError
};
diff --git a/frontend/src/lib/components/ui/input/index.ts b/frontend/src/lib/components/ui/input/index.ts
index f47b6d3..c9ffe28 100644
--- a/frontend/src/lib/components/ui/input/index.ts
+++ b/frontend/src/lib/components/ui/input/index.ts
@@ -3,5 +3,5 @@ import Root from "./input.svelte";
export {
Root,
//
- Root as Input,
+ Root as Input
};
diff --git a/frontend/src/lib/components/ui/item/index.ts b/frontend/src/lib/components/ui/item/index.ts
index 168bc3e..f3a812e 100644
--- a/frontend/src/lib/components/ui/item/index.ts
+++ b/frontend/src/lib/components/ui/item/index.ts
@@ -30,5 +30,5 @@ export {
Title as ItemTitle,
Description as ItemDescription,
Actions as ItemActions,
- Media as ItemMedia,
+ Media as ItemMedia
};
diff --git a/frontend/src/lib/components/ui/item/item-group.svelte b/frontend/src/lib/components/ui/item/item-group.svelte
index 3e58e36..c8f1841 100644
--- a/frontend/src/lib/components/ui/item/item-group.svelte
+++ b/frontend/src/lib/components/ui/item/item-group.svelte
@@ -14,7 +14,10 @@
bind:this={ref}
role="list"
data-slot="item-group"
- class={cn("gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2 group/item-group flex w-full flex-col", className)}
+ class={cn(
+ "gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2 group/item-group flex w-full flex-col",
+ className
+ )}
{...restProps}
>
{@render children?.()}
diff --git a/frontend/src/lib/components/ui/item/item-media.svelte b/frontend/src/lib/components/ui/item/item-media.svelte
index 45def5a..74e2695 100644
--- a/frontend/src/lib/components/ui/item/item-media.svelte
+++ b/frontend/src/lib/components/ui/item/item-media.svelte
@@ -7,12 +7,13 @@
variant: {
default: "bg-transparent",
icon: "[&_svg:not([class*='size-'])]:size-4",
- image: "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover",
- },
+ image:
+ "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover"
+ }
},
defaultVariants: {
- variant: "default",
- },
+ variant: "default"
+ }
});
export type ItemMediaVariant = VariantProps["variant"];
diff --git a/frontend/src/lib/components/ui/item/item-title.svelte b/frontend/src/lib/components/ui/item/item-title.svelte
index c9a0f9b..fc805af 100644
--- a/frontend/src/lib/components/ui/item/item-title.svelte
+++ b/frontend/src/lib/components/ui/item/item-title.svelte
@@ -13,7 +13,10 @@
{@render children?.()}
diff --git a/frontend/src/lib/components/ui/item/item.svelte b/frontend/src/lib/components/ui/item/item.svelte
index 8eff328..2a10b8a 100644
--- a/frontend/src/lib/components/ui/item/item.svelte
+++ b/frontend/src/lib/components/ui/item/item.svelte
@@ -7,18 +7,18 @@
variant: {
default: "border-transparent",
outline: "border-border",
- muted: "bg-muted/50 border-transparent",
+ muted: "bg-muted/50 border-transparent"
},
size: {
default: "gap-3.5 px-4 py-3.5",
sm: "gap-2.5 px-3 py-2.5",
- xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0",
- },
+ xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0"
+ }
},
defaultVariants: {
variant: "default",
- size: "default",
- },
+ size: "default"
+ }
});
export type ItemSize = VariantProps["size"];
@@ -48,7 +48,7 @@
"data-slot": "item",
"data-variant": variant,
"data-size": size,
- ...restProps,
+ ...restProps
});
diff --git a/frontend/src/lib/components/ui/kbd/index.ts b/frontend/src/lib/components/ui/kbd/index.ts
index 6aa7f07..c622e9a 100644
--- a/frontend/src/lib/components/ui/kbd/index.ts
+++ b/frontend/src/lib/components/ui/kbd/index.ts
@@ -6,5 +6,5 @@ export {
Group,
//
Root as Kbd,
- Group as KbdGroup,
+ Group as KbdGroup
};
diff --git a/frontend/src/lib/components/ui/label/index.ts b/frontend/src/lib/components/ui/label/index.ts
index 8bfca0b..2c3128c 100644
--- a/frontend/src/lib/components/ui/label/index.ts
+++ b/frontend/src/lib/components/ui/label/index.ts
@@ -3,5 +3,5 @@ import Root from "./label.svelte";
export {
Root,
//
- Root as Label,
+ Root as Label
};
diff --git a/frontend/src/lib/components/ui/popover/index.ts b/frontend/src/lib/components/ui/popover/index.ts
index 9f30922..1a8a309 100644
--- a/frontend/src/lib/components/ui/popover/index.ts
+++ b/frontend/src/lib/components/ui/popover/index.ts
@@ -13,5 +13,5 @@ export {
Root as Popover,
Content as PopoverContent,
Trigger as PopoverTrigger,
- Close as PopoverClose,
+ Close as PopoverClose
};
diff --git a/frontend/src/lib/components/ui/range-calendar/index.ts b/frontend/src/lib/components/ui/range-calendar/index.ts
index d2d258b..aea5e57 100644
--- a/frontend/src/lib/components/ui/range-calendar/index.ts
+++ b/frontend/src/lib/components/ui/range-calendar/index.ts
@@ -36,5 +36,5 @@ export {
Nav,
Month,
//
- Root as RangeCalendar,
+ Root as RangeCalendar
};
diff --git a/frontend/src/lib/components/ui/range-calendar/range-calendar-caption.svelte b/frontend/src/lib/components/ui/range-calendar/range-calendar-caption.svelte
index 944654d..bb7c7bc 100644
--- a/frontend/src/lib/components/ui/range-calendar/range-calendar-caption.svelte
+++ b/frontend/src/lib/components/ui/range-calendar/range-calendar-caption.svelte
@@ -14,7 +14,7 @@
month,
locale,
placeholder = $bindable(),
- monthIndex = 0,
+ monthIndex = 0
}: {
captionLayout: ComponentProps["captionLayout"];
months: ComponentProps["months"];
diff --git a/frontend/src/lib/components/ui/range-calendar/range-calendar.svelte b/frontend/src/lib/components/ui/range-calendar/range-calendar.svelte
index 4d917a6..573874b 100644
--- a/frontend/src/lib/components/ui/range-calendar/range-calendar.svelte
+++ b/frontend/src/lib/components/ui/range-calendar/range-calendar.svelte
@@ -94,7 +94,7 @@
{#if day}
{@render day({
day: date,
- outsideMonth: !isEqualMonth(date, month.value),
+ outsideMonth: !isEqualMonth(date, month.value)
})}
{:else}
diff --git a/frontend/src/lib/components/ui/select/index.ts b/frontend/src/lib/components/ui/select/index.ts
index 9e8d3e9..ace6bf9 100644
--- a/frontend/src/lib/components/ui/select/index.ts
+++ b/frontend/src/lib/components/ui/select/index.ts
@@ -33,5 +33,5 @@ export {
Separator as SelectSeparator,
ScrollDownButton as SelectScrollDownButton,
ScrollUpButton as SelectScrollUpButton,
- GroupHeading as SelectGroupHeading,
+ GroupHeading as SelectGroupHeading
};
diff --git a/frontend/src/lib/components/ui/separator/index.ts b/frontend/src/lib/components/ui/separator/index.ts
index 82442d2..dbfb139 100644
--- a/frontend/src/lib/components/ui/separator/index.ts
+++ b/frontend/src/lib/components/ui/separator/index.ts
@@ -3,5 +3,5 @@ import Root from "./separator.svelte";
export {
Root,
//
- Root as Separator,
+ Root as Separator
};
diff --git a/frontend/src/lib/components/ui/sheet/index.ts b/frontend/src/lib/components/ui/sheet/index.ts
index 01d40c8..29e0d0c 100644
--- a/frontend/src/lib/components/ui/sheet/index.ts
+++ b/frontend/src/lib/components/ui/sheet/index.ts
@@ -32,5 +32,5 @@ export {
Header as SheetHeader,
Footer as SheetFooter,
Title as SheetTitle,
- Description as SheetDescription,
+ Description as SheetDescription
};
diff --git a/frontend/src/lib/components/ui/sheet/sheet-content.svelte b/frontend/src/lib/components/ui/sheet/sheet-content.svelte
index 856922e..aaa91e7 100644
--- a/frontend/src/lib/components/ui/sheet/sheet-content.svelte
+++ b/frontend/src/lib/components/ui/sheet/sheet-content.svelte
@@ -5,14 +5,16 @@
variants: {
side: {
top: "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
- bottom: "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
+ bottom:
+ "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
left: "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
- right: "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
- },
+ right:
+ "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm"
+ }
},
defaultVariants: {
- side: "right",
- },
+ side: "right"
+ }
});
export type Side = VariantProps["side"];
diff --git a/frontend/src/lib/components/ui/sidebar/context.svelte.ts b/frontend/src/lib/components/ui/sidebar/context.svelte.ts
index 15248ad..6f9b4a9 100644
--- a/frontend/src/lib/components/ui/sidebar/context.svelte.ts
+++ b/frontend/src/lib/components/ui/sidebar/context.svelte.ts
@@ -53,9 +53,7 @@ class SidebarState {
};
toggle = () => {
- return this.#isMobile.current
- ? (this.openMobile = !this.openMobile)
- : this.setOpen(!this.open);
+ return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open);
};
}
diff --git a/frontend/src/lib/components/ui/sidebar/index.ts b/frontend/src/lib/components/ui/sidebar/index.ts
index 318a341..5c2e6b8 100644
--- a/frontend/src/lib/components/ui/sidebar/index.ts
+++ b/frontend/src/lib/components/ui/sidebar/index.ts
@@ -71,5 +71,5 @@ export {
Separator as SidebarSeparator,
Trigger as SidebarTrigger,
Trigger,
- useSidebar,
+ useSidebar
};
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-group-action.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-group-action.svelte
index fb84e4a..d9579fd 100644
--- a/frontend/src/lib/components/ui/sidebar/sidebar-group-action.svelte
+++ b/frontend/src/lib/components/ui/sidebar/sidebar-group-action.svelte
@@ -23,7 +23,7 @@
),
"data-slot": "sidebar-group-action",
"data-sidebar": "group-action",
- ...restProps,
+ ...restProps
});
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-group-label.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-group-label.svelte
index e292945..2b6287a 100644
--- a/frontend/src/lib/components/ui/sidebar/sidebar-group-label.svelte
+++ b/frontend/src/lib/components/ui/sidebar/sidebar-group-label.svelte
@@ -21,7 +21,7 @@
),
"data-slot": "sidebar-group-label",
"data-sidebar": "group-label",
- ...restProps,
+ ...restProps
});
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-action.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-action.svelte
index fa3fb0c..fb386c3 100644
--- a/frontend/src/lib/components/ui/sidebar/sidebar-menu-action.svelte
+++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-action.svelte
@@ -30,7 +30,7 @@
),
"data-slot": "sidebar-menu-action",
"data-sidebar": "menu-action",
- ...restProps,
+ ...restProps
});
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-button.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
index 4bef683..57e2fa2 100644
--- a/frontend/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
+++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
@@ -7,23 +7,21 @@
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
- "bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_var(--sidebar-border)] hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
+ "bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_var(--sidebar-border)] hover:shadow-[0_0_0_1px_var(--sidebar-accent)]"
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
- lg: "group-data-[collapsible=icon]:p-0! h-12 text-sm",
- },
+ lg: "group-data-[collapsible=icon]:p-0! h-12 text-sm"
+ }
},
defaultVariants: {
variant: "default",
- size: "default",
- },
+ size: "default"
+ }
});
- export type SidebarMenuButtonVariant = VariantProps<
- typeof sidebarMenuButtonVariants
- >["variant"];
+ export type SidebarMenuButtonVariant = VariantProps["variant"];
export type SidebarMenuButtonSize = VariantProps["size"];
@@ -63,7 +61,7 @@
"data-sidebar": "menu-button",
"data-size": size,
"data-active": isActive,
- ...restProps,
+ ...restProps
});
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
index 987f104..d221310 100644
--- a/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
+++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
@@ -30,7 +30,7 @@
"data-sidebar": "menu-sub-button",
"data-size": size,
"data-active": isActive,
- ...restProps,
+ ...restProps
});
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte
index 5b0d0aa..ff9ffca 100644
--- a/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte
+++ b/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte
@@ -6,7 +6,7 @@
SIDEBAR_COOKIE_MAX_AGE,
SIDEBAR_COOKIE_NAME,
SIDEBAR_WIDTH,
- SIDEBAR_WIDTH_ICON,
+ SIDEBAR_WIDTH_ICON
} from "./constants.js";
import { setSidebar } from "./context.svelte.js";
@@ -31,7 +31,7 @@
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
- },
+ }
});
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar.svelte b/frontend/src/lib/components/ui/sidebar/sidebar.svelte
index 3e9eba9..a4ce730 100644
--- a/frontend/src/lib/components/ui/sidebar/sidebar.svelte
+++ b/frontend/src/lib/components/ui/sidebar/sidebar.svelte
@@ -34,10 +34,7 @@
{@render children?.()}
{:else if sidebar.isMobile}
- sidebar.openMobile, (v) => sidebar.setOpenMobile(v)}
- {...restProps}
- >
+ sidebar.openMobile, (v) => sidebar.setOpenMobile(v)} {...restProps}>
{#each thumbItems as thumb (thumb.index)}
diff --git a/frontend/src/lib/components/ui/switch/index.ts b/frontend/src/lib/components/ui/switch/index.ts
index f5533db..f0e5fb7 100644
--- a/frontend/src/lib/components/ui/switch/index.ts
+++ b/frontend/src/lib/components/ui/switch/index.ts
@@ -3,5 +3,5 @@ import Root from "./switch.svelte";
export {
Root,
//
- Root as Switch,
+ Root as Switch
};
diff --git a/frontend/src/lib/components/ui/table/index.ts b/frontend/src/lib/components/ui/table/index.ts
index 14695c8..450c9b3 100644
--- a/frontend/src/lib/components/ui/table/index.ts
+++ b/frontend/src/lib/components/ui/table/index.ts
@@ -24,5 +24,5 @@ export {
Footer as TableFooter,
Head as TableHead,
Header as TableHeader,
- Row as TableRow,
+ Row as TableRow
};
diff --git a/frontend/src/lib/components/ui/tabs/index.ts b/frontend/src/lib/components/ui/tabs/index.ts
index 12d4327..56fe91c 100644
--- a/frontend/src/lib/components/ui/tabs/index.ts
+++ b/frontend/src/lib/components/ui/tabs/index.ts
@@ -12,5 +12,5 @@ export {
Root as Tabs,
Content as TabsContent,
List as TabsList,
- Trigger as TabsTrigger,
+ Trigger as TabsTrigger
};
diff --git a/frontend/src/lib/components/ui/tabs/tabs-list.svelte b/frontend/src/lib/components/ui/tabs/tabs-list.svelte
index 08932b6..48d14b0 100644
--- a/frontend/src/lib/components/ui/tabs/tabs-list.svelte
+++ b/frontend/src/lib/components/ui/tabs/tabs-list.svelte
@@ -2,11 +2,7 @@
import { Tabs as TabsPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
- let {
- ref = $bindable(null),
- class: className,
- ...restProps
- }: TabsPrimitive.ListProps = $props();
+ let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.ListProps = $props();
+ {...restProps}>
diff --git a/frontend/src/lib/components/ui/tooltip/index.ts b/frontend/src/lib/components/ui/tooltip/index.ts
index 313a7f0..f8dc9df 100644
--- a/frontend/src/lib/components/ui/tooltip/index.ts
+++ b/frontend/src/lib/components/ui/tooltip/index.ts
@@ -17,5 +17,5 @@ export {
Content as TooltipContent,
Trigger as TooltipTrigger,
Provider as TooltipProvider,
- Portal as TooltipPortal,
+ Portal as TooltipPortal
};
diff --git a/frontend/src/lib/dashboard/DashboardHeader.svelte b/frontend/src/lib/dashboard/DashboardHeader.svelte
index 05cf750..38284ad 100644
--- a/frontend/src/lib/dashboard/DashboardHeader.svelte
+++ b/frontend/src/lib/dashboard/DashboardHeader.svelte
@@ -1,40 +1,48 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
+ header {
+ max-width: 100vw;
+ }
+
diff --git a/frontend/src/lib/dashboard/DashboardIsNavigating.svelte b/frontend/src/lib/dashboard/DashboardIsNavigating.svelte
index 0bc6ed2..f2d55a5 100644
--- a/frontend/src/lib/dashboard/DashboardIsNavigating.svelte
+++ b/frontend/src/lib/dashboard/DashboardIsNavigating.svelte
@@ -1,22 +1,22 @@
\ No newline at end of file
+ .progress-bar-indicator {
+ height: 100%;
+ width: 100%;
+ /* this will do the magic */
+ -webkit-mask: linear-gradient(#fff 0 0);
+ mask: linear-gradient(#fff 0 0);
+ }
+ .progress-bar-indicator::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image: linear-gradient(to right, red, blue); /* your gradient here */
+ }
+
diff --git a/frontend/src/lib/dashboard/DashboardSidebar.svelte b/frontend/src/lib/dashboard/DashboardSidebar.svelte
index e27e189..4951ca2 100644
--- a/frontend/src/lib/dashboard/DashboardSidebar.svelte
+++ b/frontend/src/lib/dashboard/DashboardSidebar.svelte
@@ -1,226 +1,273 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
- Dashboard
-
- {#each navigation as item (item.title)}
- {
- if (sidebar.openMobile) {
- sidebar.setOpenMobile(false);
- }
- }}>
- {#snippet child({ props })}
-
- {#if item.icon}
-
- {/if}
- {item.title}
-
- {/snippet}
-
- {/each}
-
-
+
+
+ Dashboard
+
+ {#each navigation as item (item.title)}
+ {
+ if (sidebar.openMobile) {
+ sidebar.setOpenMobile(false);
+ }
+ }}
+ >
+ {#snippet child({ props })}
+
+ {#if item.icon}
+
+ {/if}
+ {item.title}
+
+ {/snippet}
+
+ {/each}
+
+
- {#if sidebar.open || sidebar.isMobile}
-
- Integrations
- {
- addIntegrationDropdownOpen = true;
- if (sidebar.openMobile) {
- sidebar.setOpenMobile(false);
- }
- }}>
-
- Add Project
-
-
- {#each await getMyIntegrations() as integration (`anintegration${integration.id}`)}
- {
- if (sidebar.openMobile) {
- sidebar.setOpenMobile(false);
- }
- selectedIntegration = integration;
- }}>
- {#if integration.service === "planningcenter"}
-
- {:else if integration.service === "breeze"}
-
- {:else if integration.service === "twitter"}
-
- {:else}
-
- {/if}
- {integration.prettyName}
-
-
- {/each}
-
-
- {/if}
-
+ {#if sidebar.open || sidebar.isMobile}
+
+ Integrations
+ {
+ addIntegrationDropdownOpen = true;
+ if (sidebar.openMobile) {
+ sidebar.setOpenMobile(false);
+ }
+ }}
+ >
+
+ Add Project
+
+
+ {#each await getMyIntegrations() as integration (`anintegration${integration.id}`)}
+ {
+ if (sidebar.openMobile) {
+ sidebar.setOpenMobile(false);
+ }
+ selectedIntegration = integration;
+ }}
+ >
+ {#if integration.service === "planningcenter"}
+
+ {:else if integration.service === "breeze"}
+
+ {:else if integration.service === "twitter"}
+
+ {:else}
+
+ {/if}
+ {integration.prettyName}
+
+
+ {/each}
+
+
+ {/if}
+
-
-
-
-
-
- {#snippet child({ props })}
-
+
+
+
+
-
\ No newline at end of file
+
diff --git a/frontend/src/lib/dashboard/calendar/CalAvatar.svelte b/frontend/src/lib/dashboard/calendar/CalAvatar.svelte
index 46963b9..f73fbd2 100644
--- a/frontend/src/lib/dashboard/calendar/CalAvatar.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalAvatar.svelte
@@ -1,107 +1,111 @@
-
- Calendar Avatar
- Upload an image to represent your calendar
-
-
-
-
- {#if avatarLinkBindable.length > 0 || uploadNewAvatar}
- {#if avatarLinkBindable.length > 0}
-
- {/if}
+
+ Calendar Avatar
+ Upload an image to represent your calendar
+
+
+
+
+ {#if avatarLinkBindable.length > 0 || uploadNewAvatar}
+ {#if avatarLinkBindable.length > 0}
+
+ {/if}
-
-
-
-
-
-
- {:else}
-
-
-
- {/if}
-
-
-
-
Upload a custom avatar
-
JPG, PNG or GIF. Max size 2MB. Recommended 400x400px.
-
-
-
-
- Upload Image
- ).as("file")}
- id="imageUploaderCalendar"
- type="file"
- accept="image/*"
- onchange={handleAvatarChange}
- class="absolute inset-0 opacity-0 cursor-pointer"
- />
-
- {#if uploadNewAvatar}
-
- Remove
-
- {/if}
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+ {:else}
+
+
+
+ {/if}
+
+
+
+
Upload a custom avatar
+
+ JPG, PNG or GIF. Max size 2MB. Recommended 400x400px.
+
+
+
+
+
+ Upload Image
+ ).as(
+ "file"
+ )}
+ id="imageUploaderCalendar"
+ type="file"
+ accept="image/*"
+ onchange={handleAvatarChange}
+ class="absolute inset-0 opacity-0 cursor-pointer"
+ />
+
+ {#if uploadNewAvatar}
+
+ Remove
+
+ {/if}
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/calendar/CalDisplaySettings.svelte b/frontend/src/lib/dashboard/calendar/CalDisplaySettings.svelte
index a9eb3d2..3f0f222 100644
--- a/frontend/src/lib/dashboard/calendar/CalDisplaySettings.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalDisplaySettings.svelte
@@ -1,143 +1,183 @@
-
- Display Settings
- Customize how event information is displayed
-
-
-
-
-
- View Type
- You can choose a 3 day, week, or a month view option. The smaller the view, more information can be displayed.
-
-
-
-
- 3 Day
- 7 Day
- Month
-
-
-
+
+ Display Settings
+ Customize how event information is displayed
+
+
+
-
-
\ No newline at end of file
+ {#if displaySettingsBindable.showLocation}
+
+
+ Only Show Location Title
+ Hide detailed location information
+
+
+
+
+ {/if}
+
+
+
diff --git a/frontend/src/lib/dashboard/calendar/CalEventPreview.svelte b/frontend/src/lib/dashboard/calendar/CalEventPreview.svelte
index e784a1c..a55d8cc 100644
--- a/frontend/src/lib/dashboard/calendar/CalEventPreview.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalEventPreview.svelte
@@ -1,81 +1,89 @@
-
\ No newline at end of file
+
diff --git a/frontend/src/lib/dashboard/calendar/CalFilterSettings.svelte b/frontend/src/lib/dashboard/calendar/CalFilterSettings.svelte
index 08a9626..12a9c07 100644
--- a/frontend/src/lib/dashboard/calendar/CalFilterSettings.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalFilterSettings.svelte
@@ -1,205 +1,331 @@
-
- Filter Settings
- Choose what kind of events you want to show in the feed.
-
-
-
-
-
- Only Featured Events
- Show only the events that are set to featured.
-
+
+ Filter Settings
+ Choose what kind of events you want to show in the feed.
+
+
+
-
-
- Hide Unpublished Events
- Hide the events that are not visible in the church center.
-
+
+
+ Hide Unpublished Events
+ Hide the events that are not visible in the church center.
+
-
-
-
+
+
+
-
-
-
- Enable Resource Filtering
- Most events contain information regarding the resources that are required. You can allow or block events based on its resource requirements.
-
+
-
-
-
-
- Allow Events
- Block Events
-
-
-
-
- Allow events that contain this resource.{#if filtersBindable.allowResources.length === 0}*If no resource is selected, then all events will go through and no filtering will happen. {/if}
-
-
- {#each myResources as resource (`allowAResource${resource.id}`)}
-
-
-
-
- {resource.name}{#if resource.path_name}({resource.path_name.trimEnd()}) {/if}
- {#if resource.description}
- {resource.description}
- {/if}
-
-
-
-
-
-
-
- {/each}
-
-
-
-
- Block events that contain this resource.
-
-
- {#each myResources as resource (`blockAResource${resource.id}`)}
-
-
-
-
- {resource.name}{#if resource.path_name}({resource.path_name.trimEnd()}) {/if}
- {#if resource.description}
- {resource.description}
- {/if}
-
-
-
-
-
-
-
- {/each}
-
-
-
-
+
+
+
+
+ Allow Events
+ Block Events
+
+
+
+
+ Allow events that contain this resource.{#if filtersBindable.allowResources.length === 0}*If no resource is selected, then all events will go through and no filtering
+ will happen. {/if}
+
+
+ {#each myResources as resource (`allowAResource${resource.id}`)}
+
+
+
+
+ {resource.name}{#if resource.path_name}({resource.path_name.trimEnd()}) {/if}
+ {#if resource.description}
+ {resource.description}
+ {/if}
+
+
+
+
+
+
+
+ {/each}
+
+
+
+
+ Block events that contain this resource.
+
+
+ {#each myResources as resource (`blockAResource${resource.id}`)}
+
+
+
+
+ {resource.name}{#if resource.path_name}({resource.path_name.trimEnd()}) {/if}
+ {#if resource.description}
+ {resource.description}
+ {/if}
+
+
+
+
+
+
+
+ {/each}
+
+
+
+
-
-
-
- Enable Tag Filtering
- Most events contain information regarding the tags that are required. You can allow or block events based on its tag requirements.
-
+
-
-
-
-
- Allow Events
- Block Events
-
-
-
-
- Allow events that contain this tag.{#if filtersBindable.allowTags.length === 0}*If no tag is selected, then all events will go through and no filtering will happen. {/if}
-
-
- {#each myTags as tag (`allowATag${tag.id}`)}
-
-
-
-
- {tag.name}
-
-
-
-
-
-
-
- {/each}
-
-
-
-
- Block events that contain this tag.
-
-
- {#each myTags as tag (`blockATag${tag.id}`)}
-
-
-
-
- {tag.name}
-
-
-
-
-
-
-
- {/each}
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+ Allow Events
+ Block Events
+
+
+
+
+ Allow events that contain this tag.{#if filtersBindable.allowTags.length === 0}*If no tag is selected, then all events will go through and no filtering will
+ happen. {/if}
+
+
+ {#each myTags as tag (`allowATag${tag.id}`)}
+
+
+
+
+ {tag.name}
+
+
+
+
+
+
+
+ {/each}
+
+
+
+
+ Block events that contain this tag.
+
+
+ {#each myTags as tag (`blockATag${tag.id}`)}
+
+
+
+
+ {tag.name}
+
+
+
+
+
+
+
+ {/each}
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/calendar/CalGeneralInfo.svelte b/frontend/src/lib/dashboard/calendar/CalGeneralInfo.svelte
index 78b0028..d5a3711 100644
--- a/frontend/src/lib/dashboard/calendar/CalGeneralInfo.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalGeneralInfo.svelte
@@ -1,75 +1,77 @@
-
- General Information
- Basic details about your calendar
-
-
-
- {#if calId.length > 0}
-
- {/if}
- Calendar ID
-
-
+
+ General Information
+ Basic details about your calendar
+
+
+
+ {#if calPublicIdBindable.length > 0}
+
+ {/if}
+
Calendar URL Path
+
+ {#each updateCalendarForm.fields.publicId.issues() as issue}
+
{issue.message}
+ {/each}
+
-
- {#if calendarNameBindable.length > 0}
-
- {/if}
- Calendar Name
-
-
+
+ {#if calendarNameBindable.length > 0}
+
+ {/if}
+ Calendar Name
+
+
-
- {#if calendarDescriptionBindable.length > 0}
-
- {/if}
- Description
-
-
-
-
\ No newline at end of file
+
+ {#if calendarDescriptionBindable.length > 0}
+
+ {/if}
+ Description
+
+
+
+
diff --git a/frontend/src/lib/dashboard/calendar/CalQuickActions.svelte b/frontend/src/lib/dashboard/calendar/CalQuickActions.svelte
index d8c7f44..2c2cc4b 100644
--- a/frontend/src/lib/dashboard/calendar/CalQuickActions.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalQuickActions.svelte
@@ -1,48 +1,54 @@
-
- Quick Actions
-
-
-
-
-
- Delete Calendar
-
-
-
- Are you sure absolutely sure?
-
- This action cannot be undone. This will permanently delete this calendar from our servers. All links or image feeds relying on this calendar will not work.
-
-
- Confirm Delete
-
-
-
-
-
-
\ No newline at end of file
+
+ Quick Actions
+
+
+
+
+
+ Delete Calendar
+
+
+
+ Are you sure absolutely sure?
+
+ This action cannot be undone. This will permanently delete this calendar from our
+ servers. All links or image feeds relying on this calendar will not work.
+
+
+ Confirm Delete
+
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/calendar/CalSecuritySettings.svelte b/frontend/src/lib/dashboard/calendar/CalSecuritySettings.svelte
index f6d8ca6..60176cd 100644
--- a/frontend/src/lib/dashboard/calendar/CalSecuritySettings.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalSecuritySettings.svelte
@@ -1,83 +1,96 @@
-
- Security Settings
- Control access to your calendar
-
+
+ Security Settings
+ Control access to your calendar
+
-
-
-
-
- Password Protection
-
-
Require a password to access this calendar
-
-
-
-
+
+
+
+
Password Protection
+
Require a password to access this calendar
+
+
+
+
- {#if enablePasswordBindable}
-
- {#if newPasswordBindable.length > 0}
-
- {/if}
- Calendar Password
-
-
-
- {#if passwordScreenMessageBindable.length > 0}
-
- {/if}
- Password Screen Message
-
-
- {/if}
-
+ {#if enablePasswordBindable}
+
+ {#if newPasswordBindable.length > 0}
+
+ {/if}
+ Calendar Password
+
+
+
+ {#if passwordScreenMessageBindable.length > 0}
+
+ {/if}
+ Password Screen Message
+
+
+ {/if}
+
\ No newline at end of file
+ input[type="checkbox"] {
+ accent-color: #000000;
+ }
+
diff --git a/frontend/src/lib/dashboard/calendar/CalShare.svelte b/frontend/src/lib/dashboard/calendar/CalShare.svelte
index 53787f1..ce2e4d6 100644
--- a/frontend/src/lib/dashboard/calendar/CalShare.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalShare.svelte
@@ -1,40 +1,40 @@
-
- Link
- Share this calendar with others
-
-
-
-
- /cal/{calId}
-
-
-
- Copy Link
-
-
-
- Goto Page
-
-
-
\ No newline at end of file
+
+ Link
+ Share this calendar with others
+
+
+
+
+ /cal/{calId}
+
+
+
+ Copy Link
+
+
+
+ Goto Page
+
+
+
diff --git a/frontend/src/lib/dashboard/calendar/CalStatistics.svelte b/frontend/src/lib/dashboard/calendar/CalStatistics.svelte
index ea7739d..ba9eca7 100644
--- a/frontend/src/lib/dashboard/calendar/CalStatistics.svelte
+++ b/frontend/src/lib/dashboard/calendar/CalStatistics.svelte
@@ -1,40 +1,44 @@
-
- Statistics
- Calendar performance
-
-
-
-
- Visits
- {visits}
-
-
-
-
-
-
\ No newline at end of file
+
+ Statistics
+ Calendar performance
+
+
+
+
+ Visits
+ {visits}
+
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/dynamicUrls/ADaySlot.svelte b/frontend/src/lib/dashboard/dynamicUrls/ADaySlot.svelte
index 1c97cff..54d2712 100644
--- a/frontend/src/lib/dashboard/dynamicUrls/ADaySlot.svelte
+++ b/frontend/src/lib/dashboard/dynamicUrls/ADaySlot.svelte
@@ -1,92 +1,97 @@
handleBlockClick(e, dayIndex, slotIndex)}
- role="none"
- >
-
-
handleResizeStart(e, dayIndex, slotIndex, "top")}
- >
-
-
+ data-block
+ class={cn(
+ "absolute left-0.5 right-0.5 rounded-md overflow-hidden cursor-pointer transition-shadow group",
+ "bg-ring/20 border border-ring/40",
+ isSelected && "ring-2 ring-ring shadow-lg z-20",
+ isResizing && "opacity-80"
+ )}
+ style="top:{top}px;height:{Math.max(height, 16)}px"
+ onclick={(e) => handleBlockClick(e, dayIndex, slotIndex)}
+ role="none"
+>
+
handleResizeStart(e, dayIndex, slotIndex, "top")}
+ >
+
+
-
-
- {formatTime(start)}
-
+
+
+ {formatTime(start)}
+
- {#if height > 30}
-
- {slot.link}
-
-
- {/if}
+ {#if height > 30}
+
+ {slot.link}
+
+
+ {/if}
- {#if height > 45}
-
- {formatTime(end)}
-
- {/if}
-
+ {#if height > 45}
+
+ {formatTime(end)}
+
+ {/if}
+
-
handleResizeStart(e, dayIndex, slotIndex, "bottom")}
- >
-
-
-
\ No newline at end of file
+ handleResizeStart(e, dayIndex, slotIndex, "bottom")}
+ >
+
+
+
diff --git a/frontend/src/lib/dashboard/dynamicUrls/DynamicURLEditor.svelte b/frontend/src/lib/dashboard/dynamicUrls/DynamicURLEditor.svelte
index 950d1f7..593ceeb 100644
--- a/frontend/src/lib/dashboard/dynamicUrls/DynamicURLEditor.svelte
+++ b/frontend/src/lib/dashboard/dynamicUrls/DynamicURLEditor.svelte
@@ -1,298 +1,376 @@
- {#if saveRequired}
-
-
- Save Changes?
- All the changes you made are not yet saved.
-
- Save
-
-
-
- {/if}
-
- General Settings
-
-
-
Default Redirect URL
-
-
Where visitors go when no schedule or override matches.
-
-
-
Timezone
-
-
- {currentTz}
-
-
- {#each TIMEZONES as tz (`aTZ${tz}`)}
-
- {tz.replace(/_/g, " ")}
-
- {/each}
-
-
-
Schedule times are evaluated in this timezone.
-
-
+ {#if saveRequired}
+
+
+ Save Changes?
+ All the changes you made are not yet saved.
+
+ Save
+
+
+
+ {/if}
+
+ General Settings
+
+
+
Default Redirect URL
+
+
+ Where visitors go when no schedule or override matches.
+
+
+
+
Timezone
+
+
+ {currentTz}
+
+
+ {#each TIMEZONES as tz (`aTZ${tz}`)}
+
+ {tz.replace(/_/g, " ")}
+
+ {/each}
+
+
+
Schedule times are evaluated in this timezone.
+
+
-
-
-
- Disable URL
-
-
- When disabled, visitors will see a 404 page instead of being redirected.
-
-
-
disableURL = checked}
- />
-
-
+
+
+
Disable URL
+
+ When disabled, visitors will see a 404 page instead of being redirected.
+
+
+
(disableURL = checked)}
+ />
+
+
-
- Override Redirect
-
-
-
-
- Enable Override
-
-
- When enabled, all visitors are sent to the override URL regardless of schedule.
-
-
-
enableOverride = checked}
- />
-
- {#if enableOverride}
-
- Override URL
-
-
- {/if}
-
-
+
+
+ Override Redirect
+
+
+
+
+
Enable Override
+
+ When enabled, all visitors are sent to the override URL regardless of schedule.
+
+
+
(enableOverride = checked)}
+ />
+
+ {#if enableOverride}
+
+
Override URL
+
+
+ Override expires in:
+
+
+
+ {#if selectedOverrideExpireTime === "set"}
+ {timeWhen(url.overrideExpiresIn)}
+ {:else}
+ {overrideExpiresTimes.filter(
+ (a) => a["value"] === selectedOverrideExpireTime
+ )[0]?.label}
+ {/if}
+
+
+
+ {#each overrideExpiresTimes as expireTime (expireTime.value)}
+
+ {expireTime.label}
+
+ {/each}
+
+
+
+ {#if selectedOverrideExpireTime !== "never" && selectedOverrideExpireTime !== "set"}
+ {@const estTime = Temporal.Now.zonedDateTimeISO(currentTz).add({
+ minutes: selectedOverrideExpireTimeToDate
+ })}
+
+ Est. expire time {estTime.hour.toString().padStart(2, "0")}:{estTime.minute
+ .toString()
+ .padStart(2, "0")}
+
+ {/if}
+
+
+
+ {/if}
+
+
-
- Weekly Schedule
-
-
-
-
- Enable Weekly Schedule
-
-
- Redirect to different URLs based on day and time of the week.
-
-
-
enableWeeklySchedule = checked}
- />
-
- {#if enableWeeklySchedule}
-
{
- weekSheet = newWeekSheet;
- }}
- />
- {/if}
-
-
+
+ Weekly Schedule
+
+
+
+
Enable Weekly Schedule
+
+ Redirect to different URLs based on day and time of the week.
+
+
+
(enableWeeklySchedule = checked)}
+ />
+
+ {#if enableWeeklySchedule}
+
{
+ weekSheet = newWeekSheet;
+ }}
+ />
+ {/if}
+
+
- {#if url.refs.length > 0}
-
-
- Referral Tracking
-
-
-
-
-
- Source
- Hits
- Distribution
-
-
-
- {#each url.refs.sort((a, b) => b.hits - a.hits) as ref (`aref${ref.name}`)}
-
-
-
- ?ref={ref.name}
-
-
-
- {ref.hits.toLocaleString()}
-
-
-
-
-
- {totalHits > 0 ? Math.round((ref.hits / totalHits) * 100) : 0}%
-
-
-
-
- {/each}
-
-
-
-
- {/if}
+ {#if url.refs.length > 0}
+
+
+ Referral Tracking
+
+
+
+
+
+ Source
+ Hits
+ Distribution
+
+
+
+ {#each url.refs.sort((a, b) => b.hits - a.hits) as ref (`aref${ref.name}`)}
+
+
+
+ ?ref={ref.name}
+
+
+
+ {ref.hits.toLocaleString()}
+
+
+
+
+
+ {totalHits > 0 ? Math.round((ref.hits / totalHits) * 100) : 0}%
+
+
+
+
+ {/each}
+
+
+
+
+ {/if}
-
-
- Danger Zone
-
-
-
-
Delete this Dynamic URL
-
- This action cannot be undone. All referral tracking data will be lost.
-
-
- {#if confirmDelete}
-
-
confirmDelete = false}>
- Cancel
-
-
-
- Confirm Delete
-
-
- {:else}
-
confirmDelete = true}
- class="gap-1.5 shrink-0"
- >
-
- Delete
-
- {/if}
-
-
-
\ No newline at end of file
+
+ Danger Zone
+
+
+
Delete this Dynamic URL
+
+ This action cannot be undone. All referral tracking data will be lost.
+
+
+ {#if confirmDelete}
+
+
(confirmDelete = false)}>Cancel
+
+
+ Confirm Delete
+
+
+ {:else}
+
(confirmDelete = true)}
+ class="gap-1.5 shrink-0"
+ >
+
+ Delete
+
+ {/if}
+
+
+
diff --git a/frontend/src/lib/dashboard/dynamicUrls/SingleDynamicURL.svelte b/frontend/src/lib/dashboard/dynamicUrls/SingleDynamicURL.svelte
index 8756ead..0e5b698 100644
--- a/frontend/src/lib/dashboard/dynamicUrls/SingleDynamicURL.svelte
+++ b/frontend/src/lib/dashboard/dynamicUrls/SingleDynamicURL.svelte
@@ -1,109 +1,126 @@
{
- openId = (open ? url.id : null);
- saveRequiredClosed = saveRequired ? true : false;
- }}
+ open={isOpen}
+ onOpenChange={(open) => {
+ openId = open ? url.id : null;
+ saveRequiredClosed = saveRequired ? true : false;
+ }}
>
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
- /{url.id.replace("url_", "")}
-
-
-
- {status.label}
-
- {#if saveRequiredClosed}
-
-
- Unsaved Changes
-
- {/if}
-
-
-
- {websiteURL}/go/{url.id}
-
-
+
+
+
+ /{url.id.replace("url_", "")}
+
+
+
+ {status.label}
+
+ {#if saveRequiredClosed}
+
+
+ Unsaved Changes
+
+ {/if}
+
+
+
+ {websiteURL}/go/{url.id}
+
+
-
-
-
- {totalHits.toLocaleString()}
-
-
- {slotCount} {slotCount === 1 ? "slot" : "slots"}
-
-
{url.timeZone.toString().split("/")[1]?.replace(/_/g, " ")}
-
-
-
-
+
+
+
+ {totalHits.toLocaleString()}
+
+
+ {slotCount}
+ {slotCount === 1 ? "slot" : "slots"}
+
+
{url.timeZone.toString().split("/")[1]?.replace(/_/g, " ")}
+
+
+
+
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/dynamicUrls/WeekSheetDaySlot.svelte b/frontend/src/lib/dashboard/dynamicUrls/WeekSheetDaySlot.svelte
index 230d442..fe0ce01 100644
--- a/frontend/src/lib/dashboard/dynamicUrls/WeekSheetDaySlot.svelte
+++ b/frontend/src/lib/dashboard/dynamicUrls/WeekSheetDaySlot.svelte
@@ -1,92 +1,89 @@
handleDayMouseDown(e, dayIndex)}
- role="none"
+ data-day={dayIndex}
+ class="relative border-l border-border cursor-crosshair select-none"
+ style="height: {GRID_HEIGHT}px;"
+ onmousedown={(e) => handleDayMouseDown(e, dayIndex)}
+ role="none"
>
- {#each { length: 24 }, i}
-
- {/each}
+ {#each { length: 24 }, i}
+
+ {/each}
- {#each daySlots as slot, slotIndex (`aSlot${slotIndex}`)}
-
- {/each}
+ {#each daySlots as slot, slotIndex (`aSlot${slotIndex}`)}
+
+ {/each}
- {#if previewBlock && previewBlock.dayIndex === dayIndex && previewBlock.height > 0}
-
-
-
- {formatTime(previewBlock.startMinute)} - {formatTime(previewBlock.endMinute)}
-
-
-
- {/if}
-
\ No newline at end of file
+ {#if previewBlock && previewBlock.dayIndex === dayIndex && previewBlock.height > 0}
+
+
+
+ {formatTime(previewBlock.startMinute)} - {formatTime(previewBlock.endMinute)}
+
+
+
+ {/if}
+
diff --git a/frontend/src/lib/dashboard/dynamicUrls/WeekSheetGrid.svelte b/frontend/src/lib/dashboard/dynamicUrls/WeekSheetGrid.svelte
index 6f18462..83370e4 100644
--- a/frontend/src/lib/dashboard/dynamicUrls/WeekSheetGrid.svelte
+++ b/frontend/src/lib/dashboard/dynamicUrls/WeekSheetGrid.svelte
@@ -7,43 +7,56 @@
import { Input } from "@/components/ui/input";
import WeekSheetDaySlot from "./WeekSheetDaySlot.svelte";
- const DAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
- const TOTAL_MINUTES = 1440
- const SNAP_INTERVAL = 15
- const HOUR_HEIGHT = 48 // px per hour
- const GRID_HEIGHT = 24 * HOUR_HEIGHT // total grid height
-
- let { weekSheet = $bindable(), onchange }: { weekSheet: WeekSheetTimeSlot[][], onchange: (newWeekSheet: WeekSheetTimeSlot[][]) => unknown } = $props();
+ const DAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
+ const TOTAL_MINUTES = 1440;
+ const SNAP_INTERVAL = 15;
+ const HOUR_HEIGHT = 48; // px per hour
+ const GRID_HEIGHT = 24 * HOUR_HEIGHT; // total grid height
+
+ let {
+ weekSheet = $bindable(),
+ onchange
+ }: {
+ weekSheet: WeekSheetTimeSlot[][];
+ onchange: (newWeekSheet: WeekSheetTimeSlot[][]) => unknown;
+ } = $props();
function minuteToY(minute: number) {
- return (minute / TOTAL_MINUTES) * GRID_HEIGHT
+ return (minute / TOTAL_MINUTES) * GRID_HEIGHT;
}
function yToMinute(y: number) {
- const raw = (y / GRID_HEIGHT) * TOTAL_MINUTES
- return Math.max(0, Math.min(TOTAL_MINUTES, Math.round(raw / SNAP_INTERVAL) * SNAP_INTERVAL))
+ const raw = (y / GRID_HEIGHT) * TOTAL_MINUTES;
+ return Math.max(0, Math.min(TOTAL_MINUTES, Math.round(raw / SNAP_INTERVAL) * SNAP_INTERVAL));
}
function formatTime(minute: number) {
- const h = Math.floor(minute / 60)
- const m = minute % 60
- const period = h >= 12 ? "PM" : "AM"
- const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h
- return `${h12}:${m.toString().padStart(2, "0")} ${period}`
+ const h = Math.floor(minute / 60);
+ const m = minute % 60;
+ const period = h >= 12 ? "PM" : "AM";
+ const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
+ return `${h12}:${m.toString().padStart(2, "0")} ${period}`;
}
type DragState =
- | { type: "creating"; dayIndex: number; startMinute: number; currentMinute: number }
- | { type: "resizing"; dayIndex: number; slotIndex: number; edge: "top" | "bottom"; startMinute: number; currentMinute: number }
- | null
+ | { type: "creating"; dayIndex: number; startMinute: number; currentMinute: number }
+ | {
+ type: "resizing";
+ dayIndex: number;
+ slotIndex: number;
+ edge: "top" | "bottom";
+ startMinute: number;
+ currentMinute: number;
+ }
+ | null;
type EditingSlot = {
- dayIndex: number
- slotIndex: number | "new"
- link: string
- startMinute: number
- endMinute: number
- } | null
+ dayIndex: number;
+ slotIndex: number | "new";
+ link: string;
+ startMinute: number;
+ endMinute: number;
+ } | null;
let gridRef: HTMLDivElement | null = $state(null);
let dragState: DragState | null = $state(null);
@@ -71,9 +84,7 @@
const y = getRelativeY(e, dayEl);
const minute = yToMinute(y);
- dragState = dragState
- ? { ...dragState, currentMinute: minute }
- : null;
+ dragState = dragState ? { ...dragState, currentMinute: minute } : null;
}
function handleMouseUp() {
@@ -165,7 +176,7 @@
const slot = weekSheet[dayIndex] ? weekSheet[dayIndex][slotIndex] : undefined;
if (slot) {
const startMinute = edge === "top" ? slot.startMinute : slot.endMinute;
-
+
dragState = {
type: "resizing",
dayIndex,
@@ -210,11 +221,11 @@
}
let previewBlock: {
- dayIndex: number,
- top: number,
- height: number,
- startMinute: number,
- endMinute: number
+ dayIndex: number;
+ top: number;
+ height: number;
+ startMinute: number;
+ endMinute: number;
} | null = $state(null);
$effect(() => {
@@ -225,17 +236,17 @@
height: minuteToY(Math.abs(dragState.currentMinute - dragState.startMinute)),
startMinute: Math.min(dragState.startMinute, dragState.currentMinute),
endMinute: Math.max(dragState.startMinute, dragState.currentMinute)
- }
+ };
} else {
previewBlock = null;
}
});
let resizePreview: {
- dayIndex: number,
- slotIndex: number,
- startMinute: number,
- endMinute: number
+ dayIndex: number;
+ slotIndex: number;
+ startMinute: number;
+ endMinute: number;
} | null = $state(null);
$effect(() => {
@@ -245,13 +256,13 @@
if (slot) {
let start = slot.startMinute;
let end = slot.endMinute;
-
+
if (dragState.edge === "top") {
start = Math.min(dragState.currentMinute, end - SNAP_INTERVAL);
} else {
end = Math.max(dragState.currentMinute, start + SNAP_INTERVAL);
}
-
+
resizePreview = {
dayIndex: dragState.dayIndex,
slotIndex: dragState.slotIndex,
@@ -270,20 +281,28 @@
Click and drag on the grid to create time blocks. Click a block to edit or delete it.
-
+ {/each}
+
+
+
diff --git a/frontend/src/lib/dashboard/homePage/UpcomingEvents.svelte b/frontend/src/lib/dashboard/homePage/UpcomingEvents.svelte
index 3c72b98..abaf1ed 100644
--- a/frontend/src/lib/dashboard/homePage/UpcomingEvents.svelte
+++ b/frontend/src/lib/dashboard/homePage/UpcomingEvents.svelte
@@ -1,87 +1,104 @@
-
-
-
- {"Today's Events"}
- Fetched from Your Integrations
-
-
- {todaysEvents.length} event{#if todaysEvents.length !== 1}s{/if}
-
-
-
-
- {#if todaysEvents.length > 0}
-
- {#each todaysEvents as event (`atodayevent${event.id}`)}
-
-
-
-
-
-
-
-
{event.name}
- {#if event.location}
-
-
- {event.location}
-
- {/if}
-
- {#if event.expand.tags}
-
- {#each event.expand.tags as tag (`aneventtag${tag.name}`)}
-
- {tag.name}
-
- {/each}
-
- {/if}
-
-
-
- -
-
-
-
-
- {/each}
-
- {:else}
-
-
-
-
+
+
+
+ {"Today's Events"}
+ Fetched from Your Integrations
+
+
+ {todaysEvents.length} event{#if todaysEvents.length !== 1}s{/if}
+
+
+
+
+ {#if todaysEvents.length > 0}
+
+ {#each todaysEvents as event (`atodayevent${event.id}`)}
+
+
+
+
+
+
+
+
{event.name}
+ {#if event.location}
+
+
+ {event.location}
+
+ {/if}
+
+ {#if event.expand.tags}
+
+ {#each event.expand.tags as tag (`aneventtag${tag.name}`)}
+
+ {tag.name}
+
+ {/each}
+
+ {/if}
+
+
+
+ -
+
+
+
+
+ {/each}
+
+ {:else}
+
+
+
+
-
No Events Today
+
No Events Today
-
- You haven't created any events for today. Go to your integrations and create an event.
-
-
- {/if}
-
-
\ No newline at end of file
+
+ You haven't created any events for today. Go to your integrations and create an event.
+
+
+ {/if}
+
+
diff --git a/frontend/src/lib/dashboard/homePage/ViewItem.svelte b/frontend/src/lib/dashboard/homePage/ViewItem.svelte
index c8c4883..5359978 100644
--- a/frontend/src/lib/dashboard/homePage/ViewItem.svelte
+++ b/frontend/src/lib/dashboard/homePage/ViewItem.svelte
@@ -1,59 +1,57 @@
-
-
- {#if logo !== null && logo !== undefined}
-
- {:else}
- {#if type === "calendars"}
-
- {:else if type === "event-lists"}
-
- {:else if type === "image-feeds"}
-
- {/if}
- {/if}
-
-
-
{name}
-
-
-
- {visits}
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+ {#if logo !== null && logo !== undefined}
+
+ {:else}
+ {#if type === "calendars"}
+
+ {:else if type === "event-lists"}
+
+ {:else if type === "image-feeds"}
+
+ {/if}
+ {/if}
+
+
+
{name}
+
+
+
+ {visits}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedAdditionalImages.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedAdditionalImages.svelte
index 86955af..95c3a8f 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedAdditionalImages.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedAdditionalImages.svelte
@@ -1,23 +1,34 @@
-
- Additional Images
- Add any extra pics that are not in planning center, but you want in the image feed. (Changes not reflected in Ifeed preview!)
-
-
-
-
-
\ No newline at end of file
+
+ Additional Images
+ Add any extra pics that are not in planning center, but you want in the image feed. (Changes
+ not reflected in Ifeed preview!)
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedAvatar.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedAvatar.svelte
index a62d846..c794df4 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedAvatar.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedAvatar.svelte
@@ -1,107 +1,111 @@
-
- Feed Avatar
- Upload an image to represent your image feed
-
-
-
-
- {#if avatarLinkBindable.length > 0 || uploadNewAvatar}
- {#if avatarLinkBindable.length > 0}
-
- {/if}
+
+ Feed Avatar
+ Upload an image to represent your image feed
+
+
+
+
+ {#if avatarLinkBindable.length > 0 || uploadNewAvatar}
+ {#if avatarLinkBindable.length > 0}
+
+ {/if}
-
-
-
-
-
-
- {:else}
-
-
-
- {/if}
-
-
-
-
Upload a custom avatar
-
JPG, PNG or GIF. Max size 2MB. Recommended 400x400px.
-
-
-
-
- Upload Image
- ).as("file")}
- id="imageUploaderImageFeed"
- type="file"
- accept="image/*"
- onchange={handleAvatarChange}
- class="absolute inset-0 opacity-0 cursor-pointer"
- />
-
- {#if uploadNewAvatar}
-
- Remove
-
- {/if}
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+ {:else}
+
+
+
+ {/if}
+
+
+
+
Upload a custom avatar
+
+ JPG, PNG or GIF. Max size 2MB. Recommended 400x400px.
+
+
+
+
+
+ Upload Image
+ ).as(
+ "file"
+ )}
+ id="imageUploaderImageFeed"
+ type="file"
+ accept="image/*"
+ onchange={handleAvatarChange}
+ class="absolute inset-0 opacity-0 cursor-pointer"
+ />
+
+ {#if uploadNewAvatar}
+
+ Remove
+
+ {/if}
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedCopyIframe.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedCopyIframe.svelte
index 5f12e33..750051f 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedCopyIframe.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedCopyIframe.svelte
@@ -1,67 +1,80 @@
-
-
- Copy Iframe
-
-
-
- Embed Image Feed
-
- Customize and copy the iframe code to embed this feed on your website.
-
-
+
+
+ Copy Iframe
+
+
+
+ Embed Image Feed
+
+ Customize and copy the iframe code to embed this feed on your website.
+
+
-
-
Generated Code
-
-
{generateIframeCode(`${infosectionsLink}/ifeed/${feedId}`, `Infosections Feed | ${feedName}`, feedId, infosectionsLink, true)}
-
-
+
+
Generated Code
+
+
{generateIframeCode(
+ `${infosectionsLink}/ifeed/${feedId}`,
+ `Infosections Feed | ${feedName}`,
+ feedId,
+ infosectionsLink,
+ true
+ )}
+
+
-
- iframeDialogOpen = false}>Cancel
-
- {#if iframeCopied}
-
- Copied!
- {:else}
-
- Copy Code
- {/if}
-
-
-
-
\ No newline at end of file
+
+ (iframeDialogOpen = false)}>Cancel
+
+ {#if iframeCopied}
+
+ Copied!
+ {:else}
+
+ Copy Code
+ {/if}
+
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedDisplaySettings.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedDisplaySettings.svelte
index 5291947..7a512c9 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedDisplaySettings.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedDisplaySettings.svelte
@@ -1,101 +1,137 @@
-
- Display Settings
- Customize how event information is displayed
-
-
-
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedFilterSettings.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedFilterSettings.svelte
index a843963..089cb08 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedFilterSettings.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedFilterSettings.svelte
@@ -1,72 +1,90 @@
-
- Filter Settings
- Choose what kind of events you want to show in the feed (Changes not reflected in Ifeed preview!)
-
-
-
-
-
- Only Featured Events
- Show only the events that are set to featured.
-
+
+ Filter Settings
+ Choose what kind of events you want to show in the feed (Changes not reflected in Ifeed
+ preview!)
+
+
+
-
-
- Hide Unpublished Events
- Hide the events that are not visible in the church center.
-
+
+
+ Hide Unpublished Events
+ Hide the events that are not visible in the church center.
+
-
-
-
+
+
+
-
-
- Hide Recurring Events
- Hide events that are recurring but show only the first instance of the event in the feed.
-
+
+
+ Hide Recurring Events
+ Hide events that are recurring but show only the first instance of the event in the
+ feed.
+
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedGeneralInfo.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedGeneralInfo.svelte
index f79bd13..13efa1c 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedGeneralInfo.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedGeneralInfo.svelte
@@ -1,75 +1,66 @@
-
- General Information
- Basic details about your image feed
-
-
-
- {#if feedId.length > 0}
-
- {/if}
- Feed ID
-
-
+
+ General Information
+ Basic details about your image feed
+
+
+
+ {#if feedId.length > 0}
+
+ {/if}
+ Feed ID
+
+
-
- {#if feedNameBindable.length > 0}
-
- {/if}
- Feed Name
-
-
+
+ {#if feedNameBindable.length > 0}
+
+ {/if}
+ Feed Name
+
+
-
- {#if feedDescriptionBindable.length > 0}
-
- {/if}
- Description
-
-
-
-
\ No newline at end of file
+
+ {#if feedDescriptionBindable.length > 0}
+
+ {/if}
+ Description
+
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedIncludedCalendars.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedIncludedCalendars.svelte
index fa4dd00..277708a 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedIncludedCalendars.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedIncludedCalendars.svelte
@@ -1,25 +1,34 @@
-
- Included Calendars
- You can also include 1 or more calendars that you have created in the calendars page! (Changes not reflected in Ifeed preview!)
-
-
-
-
-
\ No newline at end of file
+
+ Included Calendars
+ You can also include 1 or more calendars that you have created in the calendars page! (Changes not reflected in Ifeed preview!)
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedPreviewCard.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedPreviewCard.svelte
index 757f562..5485837 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedPreviewCard.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedPreviewCard.svelte
@@ -1,66 +1,76 @@
- {#if browser}
-
- {/if}
+ {#if browser}
+
+ {/if}
-
- Ifeed Preview Grid
- See the current changes in this preview before saving the settings.
-
-
-
-
-
-
-
- Debugger
- Show extra information in the ifeed preview.
-
- {
- if (displaySettings && previewIFrame && previewIFrame.contentWindow) {
- previewIFrame.contentWindow.postMessage({ call: 'toggleDebug', value: checked ? "1" : "0" });
- }
- }}
- />
-
-
-
\ No newline at end of file
+
+ Ifeed Preview Grid
+ See the current changes in this preview before saving the settings.
+
+
+
+
+
+
+
+ Debugger
+ Show extra information in the ifeed preview.
+
+ {
+ if (displaySettings && previewIFrame && previewIFrame.contentWindow) {
+ previewIFrame.contentWindow.postMessage({
+ call: "toggleDebug",
+ value: checked ? "1" : "0"
+ });
+ }
+ }}
+ />
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedQuickActions.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedQuickActions.svelte
index 0fb6c3d..9d67309 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedQuickActions.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedQuickActions.svelte
@@ -1,48 +1,56 @@
-
- Quick Actions
-
-
-
-
-
- Delete Feed
-
-
-
- Are you sure absolutely sure?
-
- This action cannot be undone. This will permanently delete this image feed from our servers. All links relying on this feed will not work.
-
-
- Confirm Delete
-
-
-
-
-
-
\ No newline at end of file
+
+ Quick Actions
+
+
+
+
+
+ Delete Feed
+
+
+
+ Are you sure absolutely sure?
+
+ This action cannot be undone. This will permanently delete this image feed from our
+ servers. All links relying on this feed will not work.
+
+
+ Confirm Delete
+
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedShare.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedShare.svelte
index 01b2565..19cd4e6 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedShare.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedShare.svelte
@@ -1,42 +1,46 @@
-
- Link
- Share this feed with others
-
-
-
-
- /ifeed/{feedId}
-
-
-
-
-
-
-
- Goto Page
-
-
-
\ No newline at end of file
+
+ Link
+ Share this feed with others
+
+
+
+
+ /ifeed/{feedId}
+
+
+
+
+
+
+
+ Goto Page
+
+
+
diff --git a/frontend/src/lib/dashboard/imageFeed/IFeedStatistics.svelte b/frontend/src/lib/dashboard/imageFeed/IFeedStatistics.svelte
index ed5f429..dd2c979 100644
--- a/frontend/src/lib/dashboard/imageFeed/IFeedStatistics.svelte
+++ b/frontend/src/lib/dashboard/imageFeed/IFeedStatistics.svelte
@@ -1,40 +1,44 @@
-
- Statistics
- Image Feed performance
-
-
-
-
- Visits
- {visits}
-
-
-
-
-
-
\ No newline at end of file
+
+ Statistics
+ Image Feed performance
+
+
+
+
+ Visits
+ {visits}
+
+
+
+
+
+
diff --git a/frontend/src/lib/dashboard/integrations/AddIntegrationDialog.svelte b/frontend/src/lib/dashboard/integrations/AddIntegrationDialog.svelte
index f6b6355..9bf9ea8 100644
--- a/frontend/src/lib/dashboard/integrations/AddIntegrationDialog.svelte
+++ b/frontend/src/lib/dashboard/integrations/AddIntegrationDialog.svelte
@@ -1,61 +1,68 @@
-
-
-
- Add Integration
- Connect a new service to sync events automatically.
-
+
+
+
+ Add Integration
+ Connect a new service to sync events automatically.
+
-
- {#each availableIntegrations as integration (`anavailableintegration${integration.id}`)}
- {@const isConnected = (await getMyIntegrations()).filter((int) => int.service === integration.id).length > 0}
-
- {/each}
-
-
-
\ No newline at end of file
+
+ {#each availableIntegrations as integration (`anavailableintegration${integration.id}`)}
+ {@const isConnected =
+ (await getMyIntegrations()).filter((int) => int.service === integration.id).length > 0}
+
+ {/each}
+
+
+
diff --git a/frontend/src/lib/dashboard/integrations/BreezeIcon.svelte b/frontend/src/lib/dashboard/integrations/BreezeIcon.svelte
index e488c3f..04ec597 100644
--- a/frontend/src/lib/dashboard/integrations/BreezeIcon.svelte
+++ b/frontend/src/lib/dashboard/integrations/BreezeIcon.svelte
@@ -1,5 +1,5 @@
-
\ No newline at end of file
+
diff --git a/frontend/src/lib/dashboard/integrations/IntegrationIcon.svelte b/frontend/src/lib/dashboard/integrations/IntegrationIcon.svelte
index 9650a22..a36eb31 100644
--- a/frontend/src/lib/dashboard/integrations/IntegrationIcon.svelte
+++ b/frontend/src/lib/dashboard/integrations/IntegrationIcon.svelte
@@ -1,40 +1,48 @@
- {#if icon === "planningcenter"}
-
- {:else if icon === "twitter"}
-
- X
-
-
- {:else}
- {iconLetters[icon] || "?"}
- {/if}
-
\ No newline at end of file
+ {#if icon === "planningcenter"}
+
+ {:else if icon === "twitter"}
+
+ X
+
+
+ {:else}
+ {iconLetters[icon] || "?"}
+ {/if}
+
diff --git a/frontend/src/lib/dashboard/integrations/IntegrationInfoDialog.svelte b/frontend/src/lib/dashboard/integrations/IntegrationInfoDialog.svelte
index 60c5ffe..a498b41 100644
--- a/frontend/src/lib/dashboard/integrations/IntegrationInfoDialog.svelte
+++ b/frontend/src/lib/dashboard/integrations/IntegrationInfoDialog.svelte
@@ -1,91 +1,100 @@
- {
- if (!open) integration = null;
-}}>
- {#if integration}
-
-
-
-
-
- {integration.prettyName}
- Manage your {integration.prettyName} integration
-
-
-
+ {
+ if (!open) integration = null;
+ }}
+>
+ {#if integration}
+
+
+
+
+
+ {integration.prettyName}
+ Manage your {integration.prettyName} integration
+
+
+
-
-
- Status
-
-
+
+
+ Status
+
+
- {#if integration.service === "planningcenter"}
-
- Next sync event
- {timeWhen((new Date(integration.lastEventsFetch)).setHours((new Date(integration.lastEventsFetch)).getHours() + 1))}
-
- {/if}
-
+ {#if integration.service === "planningcenter"}
+
+ Next sync event
+ {timeWhen(
+ new Date(integration.lastEventsFetch).setHours(
+ new Date(integration.lastEventsFetch).getHours() + 1
+ )
+ )}
+
+ {/if}
+
-
- {#if integration.service === "planningcenter"}
-
-
- {#if syncing}
-
- {:else}
-
- {/if}
- Sync Now
-
-
- {/if}
-
-
-
- {/if}
-
\ No newline at end of file
+
+ {#if integration.service === "planningcenter"}
+
+
+ {#if syncing}
+
+ {:else}
+
+ {/if}
+ Sync Now
+
+
+ {/if}
+
+
+
+ {/if}
+
diff --git a/frontend/src/lib/dashboard/integrations/StatusBadge.svelte b/frontend/src/lib/dashboard/integrations/StatusBadge.svelte
index e9b325e..f0f2a18 100644
--- a/frontend/src/lib/dashboard/integrations/StatusBadge.svelte
+++ b/frontend/src/lib/dashboard/integrations/StatusBadge.svelte
@@ -1,30 +1,52 @@
{#if config}
-
-
- {config.label}
-
-{/if}
\ No newline at end of file
+
+
+ {config.label}
+
+{/if}
diff --git a/frontend/src/lib/dashboard/integrations/XIcon.svelte b/frontend/src/lib/dashboard/integrations/XIcon.svelte
index 5fa95fd..fb38d78 100644
--- a/frontend/src/lib/dashboard/integrations/XIcon.svelte
+++ b/frontend/src/lib/dashboard/integrations/XIcon.svelte
@@ -1,4 +1,6 @@
- X
-
-
\ No newline at end of file
+ X
+
+
diff --git a/frontend/src/lib/event.utils.ts b/frontend/src/lib/event.utils.ts
index 0f643b5..097c738 100644
--- a/frontend/src/lib/event.utils.ts
+++ b/frontend/src/lib/event.utils.ts
@@ -2,156 +2,161 @@ import type { RecordModel } from "pocketbase";
import type { CalendarFilters } from "./cal.utils";
export type EventTimesType = {
- name: string,
- startTime: string,
- endTime: string
-}
+ name: string;
+ startTime: string;
+ endTime: string;
+};
export interface EventResourcesDBModel extends RecordModel {
- resource_id: string
- kind: "Room" | "Resource",
- name: string,
- path_name: string,
- description: string,
- quantity: number
+ resource_id: string;
+ kind: "Room" | "Resource";
+ name: string;
+ path_name: string;
+ description: string;
+ quantity: number;
}
export interface EventResourcesDBModelPrivate extends EventResourcesDBModel {
- id: string,
- owner: string
+ id: string;
+ owner: string;
}
export interface EventTagsDBModel extends RecordModel {
- tag_id: string,
- color: string,
- name: string
+ tag_id: string;
+ color: string;
+ name: string;
}
export interface EventTagsDBModelPrivate extends EventTagsDBModel {
- id: string,
- owner: string
+ id: string;
+ owner: string;
}
export interface EventDBModel extends RecordModel {
- recEventId: string,
- name: string,
- description: string,
- imageURL: string,
- registrationURL: string,
- location: string,
- times: EventTimesType[] | null,
- startTime: string,
- endTime: string,
- featured: boolean,
- visibleInChurchCenter: boolean
- recurrence: string,
- service: "planningcenter",
- resources: string[] | null
- tags: string[] | null,
+ recEventId: string;
+ name: string;
+ description: string;
+ imageURL: string;
+ registrationURL: string;
+ location: string;
+ times: EventTimesType[] | null;
+ startTime: string;
+ endTime: string;
+ featured: boolean;
+ visibleInChurchCenter: boolean;
+ recurrence: string;
+ service: "planningcenter";
+ resources: string[] | null;
+ tags: string[] | null;
}
export interface EventDBModelExpanded extends EventDBModel {
expand: {
- tags: EventTagsDBModel[] | null,
- resources: EventResourcesDBModel[] | null
- }
+ tags: EventTagsDBModel[] | null;
+ resources: EventResourcesDBModel[] | null;
+ };
}
export interface EventDBModelPrivate extends EventDBModel {
- owner: string
+ owner: string;
}
export interface EventDBModelPrivateExpanded extends EventDBModelPrivate {
expand: {
- tags: EventTagsDBModelPrivate[] | null,
- resources: EventResourcesDBModelPrivate[] | null
- }
+ tags: EventTagsDBModelPrivate[] | null;
+ resources: EventResourcesDBModelPrivate[] | null;
+ };
}
-export const eventFieldRequirementsPublic = "id,recEventId,name,description,imageURL,registrationURL,location,times,startTime,endTime,featured,visibleInChurchCenter,created,updated,resources,tags";
+export const eventFieldRequirementsPublic =
+ "id,recEventId,name,description,imageURL,registrationURL,location,times,startTime,endTime,featured,visibleInChurchCenter,created,updated,resources,tags";
export const tagsExpandRequirementsPublic = "expand.tags.tag_id,expand.tags.name,expand.tags.color";
-export const resourcesExpandRequirementsPublic = "expand.resources.resource_id,expand.resources.kind,expand.resources.name,expand.resources.path_name,expand.resources.description,expand.resources.quantity";
+export const resourcesExpandRequirementsPublic =
+ "expand.resources.resource_id,expand.resources.kind,expand.resources.name,expand.resources.path_name,expand.resources.description,expand.resources.quantity";
export function eventResourceAllowListToFilterString(allowedResources: string[]) {
let filterString = "";
- for (let i=0;i {
- let allGood = true;
-
- if (filters.enableResourceFiltering) {
- if (filters.resourceFilterType === "allow") {
- let allowedResourceFound = false;
- for (let i=0;i
- import type { ImageListCustomizations } from "@/utils";
+ import type { ImageListCustomizations } from "@/utils";
import Badge from "@/components/ui/badge/badge.svelte";
- import type { EventDBModel } from "@/event.utils";
- import { Button } from "@/components/ui/button";
- import PrettyDate from "@/PrettyDate.svelte";
- import { Temporal } from "temporal-polyfill";
+ import type { EventDBModel } from "@/event.utils";
+ import { Button } from "@/components/ui/button";
+ import PrettyDate from "@/PrettyDate.svelte";
+ import { Temporal } from "temporal-polyfill";
import { Calendar } from "@lucide/svelte";
- let {
- event,
- timeZone,
- displaySettings,
- reverse = false
- }: {
- event: EventDBModel,
- timeZone: string,
- displaySettings: ImageListCustomizations,
- reverse?: boolean
- } = $props();
-
- // svelte-ignore state_referenced_locally
- console.log(event);
- $inspect(event);
+ let {
+ event,
+ timeZone,
+ displaySettings,
+ reverse = false
+ }: {
+ event: EventDBModel;
+ timeZone: string;
+ displaySettings: ImageListCustomizations;
+ reverse?: boolean;
+ } = $props();
+
+ // svelte-ignore state_referenced_locally
+ console.log(event);
+ $inspect(event);
-
-
-
-
-
-
- {#if displaySettings.displayStyle === "expanded" && event.featured}
-
FEATURED EVENT
- {/if}
-
- {#if displaySettings.showEventName}
-
{event.name}
- {/if}
-
-
-
- {#if displaySettings.displayStyle === "expanded"}
-
- {/if}
-
-
-
-
- {#if event.description && displaySettings.showEventDescription}
-
{event.description}
- {/if}
-
- {#if event.registrationURL && displaySettings.showEventRegistration}
-
-
- More Info
-
-
-
-
- {/if}
-
+ {/if}
+
\ No newline at end of file
+ .card {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 61fr 39fr;
+ overflow: hidden;
+ border: 1px solid #000000;
+ grid-template-areas:
+ "image"
+ "content";
+ background-color: #ffffff;
+ }
+
+ .card.expanded {
+ background-color: #f3f3f4;
+ border: none;
+ border-radius: 20px;
+ box-shadow:
+ rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,
+ rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;
+ }
+
+ @media screen and (min-width: 850px) {
+ .card {
+ grid-template-columns: 61fr 39fr;
+ grid-template-rows: 1fr;
+ grid-template-areas: "image content";
+ }
+
+ .card.reverse {
+ grid-template-areas: "content image";
+ grid-template-columns: 39fr 61fr;
+ }
+ }
+
+ .img-parent {
+ grid-area: image;
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ position: relative;
+ overflow: hidden;
+ min-width: 0;
+ min-height: 0;
+ }
+
+ .img-parent img {
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ transition: transform 0.6s ease 10ms;
+ transform: scale(1.01);
+ }
+
+ .card:hover .img-parent img {
+ transform: scale(1.1);
+ }
+
+ .content-parent {
+ grid-area: content;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ row-gap: 3px;
+ min-width: 0;
+ min-height: 0;
+ }
+
+ .card.expanded .content-parent {
+ padding: 30px;
+ align-items: flex-start;
+ }
+
+ .card.expanded h2 {
+ font-weight: bold;
+ }
+
+ .card:not(.expanded) .event-description {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: center;
+ }
+
+ .event-description {
+ width: 100%;
+ }
+
+ .buttonparentexpanded {
+ margin-top: 20px;
+ display: flex;
+ gap: 10px;
+ }
+
+ :global(.buttonexpanded) {
+ background-color: #000000;
+ border-radius: 10px;
+ font-weight: bold;
+ width: 150px;
+ height: 40px;
+ letter-spacing: 0.15ch;
+ }
+
+ :global(.sharebuttonexpanded) {
+ border-radius: 100%;
+ height: 40px;
+ width: 40px;
+ }
+
diff --git a/frontend/src/lib/ifeedslug/CreateCustomEvent.svelte b/frontend/src/lib/ifeedslug/CreateCustomEvent.svelte
index 77dec93..58b4ae4 100644
--- a/frontend/src/lib/ifeedslug/CreateCustomEvent.svelte
+++ b/frontend/src/lib/ifeedslug/CreateCustomEvent.svelte
@@ -1,159 +1,178 @@
-
-
- Add Image
- Add an image to this feed.
-
-
\ No newline at end of file
+
+
+
+
Add Link
+
+ This adds a button that lets people click a custom link.
+
+
+
+
+ {#if showLink}
+
+
+ Link Button Text
+
+
+
+
+ Link URL
+
+
+ {/if}
+ {#each createCustomImageForm.fields.allIssues() as anIssue (`AformsubmitIssue${anIssue.message}`)}
+ {anIssue.message}
+ {/each}
+
+
+ (dialogOpen = false)} class="bg-transparent">
+ Cancel
+
+
+ {#if creatingForm}
+ Saving
+ {:else}
+ Save
+ {/if}
+
+
+
+
diff --git a/frontend/src/lib/ifeedslug/CustomImages.svelte b/frontend/src/lib/ifeedslug/CustomImages.svelte
index b69420c..3883ce2 100644
--- a/frontend/src/lib/ifeedslug/CustomImages.svelte
+++ b/frontend/src/lib/ifeedslug/CustomImages.svelte
@@ -1,111 +1,139 @@
-
+
{#if customImages.length > 0}
-
-
-
- Picture
- Has Link
-
-
+
+
+
+ Picture
+ Has Link
+
+
-
- {#each customImages as customEvent, index (`customeventrow${customEvent.id}`)}
- eventClicked(index)}>
-
-
-
-
- {customEvent.showLink ? "Yes" : "No"}
-
-
- {/each}
-
-
-
- createCustomEventDialog=true}>Add Image
-
+
+ {#each customImages as customEvent, index (`customeventrow${customEvent.id}`)}
+ eventClicked(index)}>
+
+
+
+
+ {customEvent.showLink ? "Yes" : "No"}
+
+
+ {/each}
+
+
+
+ (createCustomEventDialog = true)}
+ >Add Image
+
{:else}
-
-
-
-
-
- No Pics Yet
- You haven't added any additional images yet.
-
-
- createCustomEventDialog=true}>Add One
-
-
+
+
+
+
+
+ No Pics Yet
+ You haven't added any additional images yet.
+
+
+ (createCustomEventDialog = true)}>Add One
+
+
{/if}
-
-
- Custom Image
- Make changes to the image settings here. Click save when you're done.
-
- {#if showEvent}
- {showEvent = false}} />
- {/if}
-
- Save changes
-
-
- Delete Image
-
-
-
- Are you sure absolutely sure?
-
- This action cannot be undone. This will permanently delete this image from our servers.
-
-
- Confirm Delete
-
-
-
-
-
-
+
+
+ Custom Image
+ Make changes to the image settings here. Click save when you're done.
+
+ {#if showEvent}
+ {
+ showEvent = false;
+ }}
+ />
+ {/if}
+
+ Save changes
+
+ Delete Image
+
+
+ Are you sure absolutely sure?
+
+ This action cannot be undone. This will permanently delete this image from our
+ servers.
+
+
+ Confirm Delete
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/frontend/src/lib/ifeedslug/UpdateCustomEvent.svelte b/frontend/src/lib/ifeedslug/UpdateCustomEvent.svelte
index 6bea7e5..08c3766 100644
--- a/frontend/src/lib/ifeedslug/UpdateCustomEvent.svelte
+++ b/frontend/src/lib/ifeedslug/UpdateCustomEvent.svelte
@@ -1,175 +1,207 @@
{
- const savingChangesToast = toast.loading("Saving changes!");
- try {
- await submit();
- toast.dismiss(savingChangesToast);
- if (!updateCustomImageForm.fields.allIssues()) {
- form.reset();
- clearFileInput(document.getElementById("imageUploaderIFeed"));
- uploadNewEventPicture = null;
- close();
- toast.success("Success.");
- }
- } catch (err) {
- console.log(err);
- toast.dismiss(savingChangesToast);
- toast.error("An error occured.");
- }
- })}
- enctype="multipart/form-data"
- class="grid flex-1 auto-rows-min gap-5 px-4 w-full overflow-y-auto"
+ id="updateEventForm"
+ {...updateCustomImageForm.enhance(async ({ submit, form }) => {
+ const savingChangesToast = toast.loading("Saving changes!");
+ try {
+ await submit();
+ toast.dismiss(savingChangesToast);
+ if (!updateCustomImageForm.fields.allIssues()) {
+ form.reset();
+ clearFileInput(document.getElementById("imageUploaderIFeed"));
+ uploadNewEventPicture = null;
+ close();
+ toast.success("Success.");
+ }
+ } catch (err) {
+ console.log(err);
+ toast.dismiss(savingChangesToast);
+ toast.error("An error occured.");
+ }
+ })}
+ enctype="multipart/form-data"
+ class="grid flex-1 auto-rows-min gap-5 px-4 w-full overflow-y-auto"
>
- {#if currentCustomImage.id}
-
- {/if}
- {#if currentFeedID}
-
- {/if}
-
+ {#if currentCustomImage.id}
+
+ {/if}
+ {#if currentFeedID}
+
+ {/if}
+
-
- {#each updateCustomImageForm.fields.allIssues() as anIssue (`AformsubmitIssue${anIssue.message}`)}
-
{anIssue.message}
- {/each}
-
- {#if eventPictureLink.length > 0 || uploadNewEventPicture}
-
-
-
-
-
-
- {:else}
-
- {/if}
-
-
-
-
Upload an image
-
- JPG, PNG or GIF. Max size 2MB. Recommended 960x540px.
-
-
-
-
-
- Upload Image
- ).as("file")}
- id="imageUploaderIFeed"
- type="file"
- accept="image/*"
- onchange={handleEventPictureChange}
- class="absolute inset-0 opacity-0 cursor-pointer"
- />
-
- {#if uploadNewEventPicture}
-
- Remove
-
- {/if}
-
-
-
+
+ {#each updateCustomImageForm.fields.allIssues() as anIssue (`AformsubmitIssue${anIssue.message}`)}
+
{anIssue.message}
+ {/each}
+
+ {#if eventPictureLink.length > 0 || uploadNewEventPicture}
+
+
+
+
+
+
+ {:else}
+
+ {/if}
+
+
+
+
Upload an image
+
+ JPG, PNG or GIF. Max size 2MB. Recommended 960x540px.
+
+
+
+
+
+ Upload Image
+
+ ).as("file")}
+ id="imageUploaderIFeed"
+ type="file"
+ accept="image/*"
+ onchange={handleEventPictureChange}
+ class="absolute inset-0 opacity-0 cursor-pointer"
+ />
+
+ {#if uploadNewEventPicture}
+
+ Remove
+
+ {/if}
+
+
+
-
-
-
- Show Link
-
-
-
+
+
+
+ Show Link
+
+
+
- {#if showLink}
-
-
- Name*
-
-
+ {#if showLink}
+
+
+ Name*
+
+
-
-
- Link URL*
-
-
- {/if}
+
+
+ Link URL*
+
+
+ {/if}
- {#each imageFeeds as imageFeed (`includeInAnImageFeed${imageFeed.id}`)}
-
-
- Include in "{imageFeed.name}" feed.
-
- {/each}
-
\ No newline at end of file
+ {#each imageFeeds as imageFeed (`includeInAnImageFeed${imageFeed.id}`)}
+
+
+ Include in "{imageFeed.name}" feed.
+
+ {/each}
+
diff --git a/frontend/src/lib/landingPage/AlphaWarning.svelte b/frontend/src/lib/landingPage/AlphaWarning.svelte
index c4ddde1..b17602b 100644
--- a/frontend/src/lib/landingPage/AlphaWarning.svelte
+++ b/frontend/src/lib/landingPage/AlphaWarning.svelte
@@ -1,12 +1,14 @@
{#if showWarning}
-
-
Warning. This website is currently in the alpha stage. Not all features are implemented yet.
-
showWarning = false}>
-
-{/if}
\ No newline at end of file
+
+
+ Warning. This website is currently in the alpha stage. Not all features are implemented yet.
+
+
(showWarning = false)}>
+
+{/if}
diff --git a/frontend/src/lib/landingPage/CTA.svelte b/frontend/src/lib/landingPage/CTA.svelte
index cd98d42..f37e2a3 100644
--- a/frontend/src/lib/landingPage/CTA.svelte
+++ b/frontend/src/lib/landingPage/CTA.svelte
@@ -1,34 +1,38 @@
+
-
-
-
- Ready to transform your scheduling?
-
-
- Join many professionals who have streamlined their calendar management with InfoSections. Start your
- free trial today—no credit card required.
-
-
-
-
- Start Free Trial
-
-
-
-
- Contact Sales
-
-
-
-
-
\ No newline at end of file
+
+
+
+ Ready to transform your scheduling?
+
+
+ Join many professionals who have streamlined their calendar management with InfoSections.
+ Start your free trial today—no credit card required.
+
+
+
+
+ Start Free Trial
+
+
+
+
+ Contact Sales
+
+
+
+
+
diff --git a/frontend/src/lib/landingPage/Features.svelte b/frontend/src/lib/landingPage/Features.svelte
index 0dfd8e1..36d03b3 100644
--- a/frontend/src/lib/landingPage/Features.svelte
+++ b/frontend/src/lib/landingPage/Features.svelte
@@ -1,49 +1,55 @@
-
-
-
- Everything you need to show only what you need
-
-
- Powerful features designed to filter and select the events that you want displayed in your custom calendar.
-
-
+
+
+
+ Everything you need to show only what you need
+
+
+ Powerful features designed to filter and select the events that you want displayed in your
+ custom calendar.
+
+
-
- {#each features as feature, index (`feature${index}`)}
-
-
-
-
-
-
{feature.title}
-
{feature.description}
-
-
- {/each}
-
-
-
\ No newline at end of file
+
+ {#each features as feature, index (`feature${index}`)}
+
+
+
+
+
+
{feature.title}
+
{feature.description}
+
+
+ {/each}
+
+
+
diff --git a/frontend/src/lib/landingPage/Header.svelte b/frontend/src/lib/landingPage/Header.svelte
index d19a938..d81e47f 100644
--- a/frontend/src/lib/landingPage/Header.svelte
+++ b/frontend/src/lib/landingPage/Header.svelte
@@ -1,32 +1,48 @@
-
-
-
-
-
- InfoSections
-
+
\ No newline at end of file
+ {#if loggedIn}
+
Dashboard
+ {:else}
+
+ Get Started
+ Login
+
+ {/if}
+
+
+
diff --git a/frontend/src/lib/landingPage/Hero.svelte b/frontend/src/lib/landingPage/Hero.svelte
index 02d0953..15aeb50 100644
--- a/frontend/src/lib/landingPage/Hero.svelte
+++ b/frontend/src/lib/landingPage/Hero.svelte
@@ -1,103 +1,108 @@
-
-
-
-
-
- Customization made simple
-
+
+
+
+
+
+ Customization made simple
+
-
- View events. A better way.
-
+
+ View events. A better way.
+
-
- We offer a unified view of every event, sortable by location, tags, and custom filters so you can focus on what's happening, where it matters.
-
+
+ We offer a unified view of every event, sortable by location, tags, and custom filters so
+ you can focus on what's happening, where it matters.
+
-
-
-
- Start Free Trial
-
-
-
-
- Watch Demo
-
-
+
+
+
+ Start Free Trial
+
+
+
+
Watch Demo
+
-
-
-
Unlimited
-
Custom Calendars
-
-
-
-
50k+
-
Calendar Views
-
-
-
+
+
+
Unlimited
+
Custom Calendars
+
+
+
+
50k+
+
Calendar Views
+
+
+
-
-
-
+
+
+
-
-
-
Total Visits
-
107
-
Visits for your calendars
-
-
-
Custom Calendars
-
3
-
Customized calendars
-
-
+
+
+
Total Visits
+
107
+
Visits for your calendars
+
+
+
Custom Calendars
+
3
+
Customized calendars
+
+
-
-
Most Popular
- {#each mostPopular as pop, index (`mostPopular${index}`)}
-
-
-
-
-
{pop.name}
-
{pop.visits}
-
-
-
- View
-
-
- {/each}
-
-
-
-
-
-
\ No newline at end of file
+
+
Most Popular
+ {#each mostPopular as pop, index (`mostPopular${index}`)}
+
+
+
+
+
{pop.name}
+
{pop.visits}
+
+
+
View
+
+ {/each}
+
+
+
+
+
+
diff --git a/frontend/src/lib/landingPage/Stats.svelte b/frontend/src/lib/landingPage/Stats.svelte
index 01801c9..7eed769 100644
--- a/frontend/src/lib/landingPage/Stats.svelte
+++ b/frontend/src/lib/landingPage/Stats.svelte
@@ -1,22 +1,22 @@
-
-
- {#each stats as stat, index (`stats${index}`)}
-
-
{stat.value}
-
{stat.label}
-
{stat.company}
-
- {/each}
-
-
-
\ No newline at end of file
+
+
+ {#each stats as stat, index (`stats${index}`)}
+
+
{stat.value}
+
{stat.label}
+
{stat.company}
+
+ {/each}
+
+
+
diff --git a/frontend/src/lib/landingPage/authComponents/LoginForm.svelte b/frontend/src/lib/landingPage/authComponents/LoginForm.svelte
index dd2558a..f4830c7 100644
--- a/frontend/src/lib/landingPage/authComponents/LoginForm.svelte
+++ b/frontend/src/lib/landingPage/authComponents/LoginForm.svelte
@@ -1,78 +1,79 @@
diff --git a/frontend/src/lib/landingPage/authComponents/SignupForm.svelte b/frontend/src/lib/landingPage/authComponents/SignupForm.svelte
index 19e6df3..b985a3b 100644
--- a/frontend/src/lib/landingPage/authComponents/SignupForm.svelte
+++ b/frontend/src/lib/landingPage/authComponents/SignupForm.svelte
@@ -1,97 +1,106 @@
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
index e5bb433..447873a 100644
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -19,90 +19,121 @@ export type WithElementRef = T & { ref?:
/**
* This function get the url that is passed and makes a fetch request to recieve the file. The function then returns the file in a blob format.
- * @param fileLink
+ * @param fileLink
* @returns Blob
*/
-export async function fetchFileFromURL(fileLink: string): Promise<{error: false, blob: Blob} | {error: true, msg: unknown}> {
+export async function fetchFileFromURL(
+ fileLink: string
+): Promise<{ error: false; blob: Blob } | { error: true; msg: unknown }> {
try {
const res = await fetch(fileLink);
const blob = await res.blob();
return {
error: false,
blob
- }
+ };
} catch (err) {
return {
error: true,
msg: err
- }
+ };
}
}
// Don't judge
export function clearFileInput(ctrl: HTMLElement | null) {
- if (ctrl === null) return
+ if (ctrl === null) return;
try {
//@ts-ignore
ctrl.value = null;
//@ts-ignore
if (ctrl.value) {
//@ts-ignore
- ctrl.parentNode.replaceChild(ctrl.cloneNode(true), ctrl);
- }
- } catch(ex) {
- }
+ ctrl.parentNode.replaceChild(ctrl.cloneNode(true), ctrl);
+ }
+ } catch (ex) {}
}
-export const LONGDAYTOSTRING = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
-export const MONTHTOSTRING = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+export const LONGDAYTOSTRING = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday"
+];
+export const MONTHTOSTRING = [
+ "",
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec"
+];
export function dateRangeOverlaps(a_start: number, a_end: number, b_start: number, b_end: number) {
- if (a_start <= b_start && b_start <= a_end) return true; // b starts in a
- if (a_start <= b_end && b_end <= a_end) return true; // b ends in a
- if (b_start < a_start && a_end < b_end) return true; // a in b
- return false;
+ if (a_start <= b_start && b_start <= a_end) return true; // b starts in a
+ if (a_start <= b_end && b_end <= a_end) return true; // b ends in a
+ if (b_start < a_start && a_end < b_end) return true; // a in b
+ return false;
}
export interface CustomImageIFeedDBModel extends RecordModel {
- picture: string | File,
- registrationURL: string,
- showLink: boolean,
- linkText: string,
- imageFeed: string[],
- owner: string
+ picture: string | File;
+ registrationURL: string;
+ showLink: boolean;
+ linkText: string;
+ imageFeed: string[];
+ owner: string;
}
export interface ImageFeedDBModel extends RecordModel {
- name: string,
- owner: string,
- logo: string | File,
- visits: number,
- description: string,
- displaySettings: ImageFeedCustomizations,
- created: string,
- updated: string,
- filters: ImageFeedFilters,
- additionalCalendars: string[]
+ name: string;
+ owner: string;
+ logo: string | File;
+ visits: number;
+ description: string;
+ displaySettings: ImageFeedCustomizations;
+ created: string;
+ updated: string;
+ filters: ImageFeedFilters;
+ additionalCalendars: string[];
}
export interface EventListDBModel extends RecordModel {
- name: string,
- owner: string,
- logo: string | File,
- visits: number,
- description: string,
- displaySettings: ImageListCustomizations,
- created: string,
- updated: string,
- filters: EventListFilters
+ name: string;
+ owner: string;
+ logo: string | File;
+ visits: number;
+ description: string;
+ displaySettings: ImageListCustomizations;
+ created: string;
+ updated: string;
+ filters: EventListFilters;
}
// Dont you dare judge
export function toggleFullScreen() {
- const elem = document.documentElement
- // ## The below if statement seems to work better ## if ((document.fullScreenElement && document.fullScreenElement !== null) || (document.msfullscreenElement && document.msfullscreenElement !== null) || (!document.mozFullScreen && !document.webkitIsFullScreen)) {
- //@ts-ignore
- if ((document.fullScreenElement !== undefined && document.fullScreenElement === null) || (document.msFullscreenElement !== undefined && document.msFullscreenElement === null) || (document.mozFullScreen !== undefined && !document.mozFullScreen) || (document.webkitIsFullScreen !== undefined && !document.webkitIsFullScreen)) {
+ const elem = document.documentElement;
+ if (
+ //@ts-ignore
+ (document.fullScreenElement !== undefined && document.fullScreenElement === null) ||
+ //@ts-ignore
+ (document.msFullscreenElement !== undefined && document.msFullscreenElement === null) ||
+ //@ts-ignore
+ (document.mozFullScreen !== undefined && !document.mozFullScreen) ||
+ //@ts-ignore
+ (document.webkitIsFullScreen !== undefined && !document.webkitIsFullScreen)
+ ) {
//@ts-ignore
if (elem.requestFullScreen) {
//@ts-ignore
@@ -110,41 +141,48 @@ export function toggleFullScreen() {
//@ts-ignore
} else if (elem.mozRequestFullScreen) {
//@ts-ignore
- elem.mozRequestFullScreen();//@ts-ignore
- } else if (elem.webkitRequestFullScreen) {//@ts-ignore
- elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);//@ts-ignore
- } else if (elem.msRequestFullscreen) {//@ts-ignore
- elem.msRequestFullscreen();//@ts-ignore
+ elem.mozRequestFullScreen(); //@ts-ignore
+ } else if (elem.webkitRequestFullScreen) {
+ //@ts-ignore
+ elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); //@ts-ignore
+ } else if (elem.msRequestFullscreen) {
+ //@ts-ignore
+ elem.msRequestFullscreen(); //@ts-ignore
}
- } else {//@ts-ignore
- if (document.cancelFullScreen) {//@ts-ignore
- document.cancelFullScreen();//@ts-ignore
- } else if (document.mozCancelFullScreen) {//@ts-ignore
- document.mozCancelFullScreen();//@ts-ignore
- } else if (document.webkitCancelFullScreen) {//@ts-ignore
- document.webkitCancelFullScreen();//@ts-ignore
- } else if (document.msExitFullscreen) {//@ts-ignore
+ } else {
+ //@ts-ignore
+ if (document.cancelFullScreen) {
+ //@ts-ignore
+ document.cancelFullScreen(); //@ts-ignore
+ } else if (document.mozCancelFullScreen) {
+ //@ts-ignore
+ document.mozCancelFullScreen(); //@ts-ignore
+ } else if (document.webkitCancelFullScreen) {
+ //@ts-ignore
+ document.webkitCancelFullScreen(); //@ts-ignore
+ } else if (document.msExitFullscreen) {
+ //@ts-ignore
document.msExitFullscreen();
}
}
}
export type ImageFeedCustomizations = {
- showEventExtraInfo: boolean,
- showEventName: boolean,
- showEventDescription: boolean,
- showEventRegistration: boolean,
- feedDurationMS: number,
-}
+ showEventExtraInfo: boolean;
+ showEventName: boolean;
+ showEventDescription: boolean;
+ showEventRegistration: boolean;
+ feedDurationMS: number;
+};
export type ImageListCustomizations = {
- displayStyle: "minimal" | "expanded",
- showEventName: boolean,
- showEventDescription: boolean,
- showEventRegistration: boolean,
- showUpcomingEventsTextAndDesc: boolean,
- setTransparentBackground: boolean
-}
+ displayStyle: "minimal" | "expanded";
+ showEventName: boolean;
+ showEventDescription: boolean;
+ showEventRegistration: boolean;
+ showUpcomingEventsTextAndDesc: boolean;
+ setTransparentBackground: boolean;
+};
export const defaultImageListCustomizations: ImageListCustomizations = {
displayStyle: "minimal",
@@ -153,143 +191,145 @@ export const defaultImageListCustomizations: ImageListCustomizations = {
showEventRegistration: true,
showUpcomingEventsTextAndDesc: false,
setTransparentBackground: false
-}
+};
export const defaultImageFeedCustomizations: ImageFeedCustomizations = {
showEventExtraInfo: false,
showEventName: true,
showEventDescription: true,
showEventRegistration: true,
- feedDurationMS: 7000,
-}
+ feedDurationMS: 7000
+};
export type ImageFeedFilters = {
- onlyShowFeatured: boolean,
- hideUnpublished: boolean,
- hideRecurringEvents: boolean,
-}
+ onlyShowFeatured: boolean;
+ hideUnpublished: boolean;
+ hideRecurringEvents: boolean;
+};
export type EventListFilters = ImageFeedFilters;
export const defaultImageFeedFilters: ImageFeedFilters = {
onlyShowFeatured: true,
hideUnpublished: true,
- hideRecurringEvents: false,
-}
+ hideRecurringEvents: false
+};
export const defaultEventListFilters: EventListFilters = {
onlyShowFeatured: true,
hideUnpublished: true,
- hideRecurringEvents: false,
-}
+ hideRecurringEvents: false
+};
export interface UserModel extends RecordModel {
- name: string,
- email: string,
- avatar: string,
- customerId: string,
- priceId: string,
- new: boolean,
- accessLevel: "none" | "standard" | "premium",
- subscriptionURL: string
- freeTrialURL: string
+ name: string;
+ email: string;
+ avatar: string;
+ customerId: string;
+ priceId: string;
+ new: boolean;
+ accessLevel: "none" | "standard" | "premium";
+ subscriptionURL: string;
+ freeTrialURL: string;
}
export interface IntegrationModel extends RecordModel {
- prettyName: string,
- owner: string,
- service: "planningcenter" | "breeze" | "google" | "twitter"
- refreshToken: string,
- accessToken: string,
- refreshTokenExpires: number,
- accessTokenExpires: number
- lastEventsFetch: string,
- status: "connected" | "syncing" | "error" | "disconnected"
+ prettyName: string;
+ owner: string;
+ service: "planningcenter" | "breeze" | "google" | "twitter";
+ refreshToken: string;
+ accessToken: string;
+ refreshTokenExpires: number;
+ accessTokenExpires: number;
+ lastEventsFetch: string;
+ status: "connected" | "syncing" | "error" | "disconnected";
}
export function capitalizeFirstLetter(str: string) {
- if (typeof str !== 'string' || str.length === 0) {
- return str; // Handle non-string inputs or empty strings
- }
- return str.charAt(0).toUpperCase() + str.slice(1);
+ if (typeof str !== "string" || str.length === 0) {
+ return str; // Handle non-string inputs or empty strings
+ }
+ return str.charAt(0).toUpperCase() + str.slice(1);
}
export function timeAgo(dateParam: Date | string | number) {
- if (!dateParam) {
- return null;
- }
+ if (!dateParam) {
+ return null;
+ }
- const date = new Date(dateParam); // Ensure it's a Date object
- const now = new Date();
+ const date = new Date(dateParam); // Ensure it's a Date object
+ const now = new Date();
//@ts-ignore
- const diffInMs = now - date; // Difference in milliseconds
-
- const seconds = Math.round(diffInMs / 1000);
- const minutes = Math.round(seconds / 60);
- const hours = Math.round(minutes / 60);
- const days = Math.round(hours / 24);
-
- // Less than a minute ago
- if (seconds < 60) {
- return `${seconds} second${seconds === 1 ? '' : 's'} ago`;
- }
- // Less than an hour ago
- if (minutes < 60) {
- return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;
- }
- // Less than 24 hours ago (a day ago)
- if (hours < 24) {
- return `${hours} hour${hours === 1 ? '' : 's'} ago`;
- }
- // Less than a month ago (e.g., 1 day ago, 5 days ago)
- if (days < 30) { // Approximating a month as 30 days
- return `${days} day${days === 1 ? '' : 's'} ago`;
- }
-
- // Older than a month, return formatted date
- const options = { year: 'numeric', month: 'long', day: 'numeric' };
+ const diffInMs = now - date; // Difference in milliseconds
+
+ const seconds = Math.round(diffInMs / 1000);
+ const minutes = Math.round(seconds / 60);
+ const hours = Math.round(minutes / 60);
+ const days = Math.round(hours / 24);
+
+ // Less than a minute ago
+ if (seconds < 60) {
+ return `${seconds} second${seconds === 1 ? "" : "s"} ago`;
+ }
+ // Less than an hour ago
+ if (minutes < 60) {
+ return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
+ }
+ // Less than 24 hours ago (a day ago)
+ if (hours < 24) {
+ return `${hours} hour${hours === 1 ? "" : "s"} ago`;
+ }
+ // Less than a month ago (e.g., 1 day ago, 5 days ago)
+ if (days < 30) {
+ // Approximating a month as 30 days
+ return `${days} day${days === 1 ? "" : "s"} ago`;
+ }
+
+ // Older than a month, return formatted date
+ const options = { year: "numeric", month: "long", day: "numeric" };
//@ts-ignore
- return date.toLocaleDateString(undefined, options); // Use default locale
+ return date.toLocaleDateString(undefined, options); // Use default locale
}
export function timeWhen(dateParam: Date | string | number) {
- if (!dateParam) {
- return null;
- }
+ if (!dateParam) {
+ return null;
+ }
- const date = new Date(dateParam); // Ensure it's a Date object
- const now = new Date();
+ const date = new Date(dateParam); // Ensure it's a Date object
+ const now = new Date();
//@ts-ignore
- const diffInMs = date - now; // Difference in milliseconds
-
- const seconds = Math.round(diffInMs / 1000);
- const minutes = Math.round(seconds / 60);
- const hours = Math.round(minutes / 60);
- const days = Math.round(hours / 24);
-
- // Less than a minute ago
- if (seconds < 60) {
- return `${seconds} second${seconds === 1 ? '' : 's'}`;
- }
- // Less than an hour ago
- if (minutes < 60) {
- return `${minutes} minute${minutes === 1 ? '' : 's'}`;
- }
- // Less than 24 hours ago (a day ago)
- if (hours < 24) {
- return `${hours} hour${hours === 1 ? '' : 's'}`;
- }
- // Less than a month ago (e.g., 1 day ago, 5 days ago)
- if (days < 30) { // Approximating a month as 30 days
- return `${days} day${days === 1 ? '' : 's'}`;
- }
-
- // Older than a month, return formatted date
- const options = { year: 'numeric', month: 'long', day: 'numeric' };
+ const diffInMs = date - now; // Difference in milliseconds
+
+ const seconds = Math.round(diffInMs / 1000);
+ const minutes = Math.round(seconds / 60);
+ const hours = Math.round(minutes / 60);
+ const days = Math.round(hours / 24);
+
+ // Less than a minute ago
+ if (seconds < 60) {
+ return `${seconds} second${seconds === 1 ? "" : "s"}`;
+ }
+ // Less than an hour ago
+ if (minutes < 60) {
+ return `${minutes} minute${minutes === 1 ? "" : "s"}`;
+ }
+ // Less than 24 hours ago (a day ago)
+ if (hours < 24) {
+ return `${hours} hour${hours === 1 ? "" : "s"}`;
+ }
+ // Less than a month ago (e.g., 1 day ago, 5 days ago)
+ if (days < 30) {
+ // Approximating a month as 30 days
+ return `${days} day${days === 1 ? "" : "s"}`;
+ }
+
+ // Older than a month, return formatted date
+ const options = { year: "numeric", month: "long", day: "numeric" };
//@ts-ignore
- return date.toLocaleDateString(undefined, options); // Use default locale
+ return date.toLocaleDateString(undefined, options); // Use default locale
}
export async function refreshingEvents() {
@@ -306,17 +346,17 @@ export async function refreshingEvents() {
export function getDaysInMonth(date: Temporal.ZonedDateTime): Temporal.ZonedDateTime[] {
const firstDay = date.with({ day: 1 });
const lastDay = date.with({ day: date.daysInMonth });
-
+
const days: Temporal.ZonedDateTime[] = [];
-
+
// Add days from previous month to start on Sunday
const startDayOfWeek = firstDay.dayOfWeek !== 7 ? firstDay.dayOfWeek : 0;
for (let i = startDayOfWeek; i >= 1; i--) {
- const prevDate = firstDay.subtract({ days: i }).startOfDay() //new Date(year, month, -i)
- days.push(prevDate)
+ const prevDate = firstDay.subtract({ days: i }).startOfDay(); //new Date(year, month, -i)
+ days.push(prevDate);
}
-
+
// Add days of current month
for (let i = 0; i <= lastDay.day - 1; i++) {
days.push(firstDay.add({ days: i }));
@@ -332,57 +372,68 @@ export function getDaysInMonth(date: Temporal.ZonedDateTime): Temporal.ZonedDate
}
// Add days from next month to complete the grid
- const remainingDays = (rowsRequired * 7) - days.length;
+ const remainingDays = rowsRequired * 7 - days.length;
for (let i = 1; i <= remainingDays; i++) {
days.push(lastDay.add({ days: i }).startOfDay());
}
- return days
+ return days;
}
-export function getDayRange(startDate: Temporal.ZonedDateTime, days: number): Temporal.ZonedDateTime[] {
- const range: Temporal.ZonedDateTime[] = []
+export function getDayRange(
+ startDate: Temporal.ZonedDateTime,
+ days: number
+): Temporal.ZonedDateTime[] {
+ const range: Temporal.ZonedDateTime[] = [];
for (let i = 0; i < days; i++) {
range.push(startDate.add({ days: i }));
}
- return range
+ return range;
}
export function isSameDay(date1: Temporal.ZonedDateTime, date2: Temporal.ZonedDateTime): boolean {
- return (
- date1.year === date2.year &&
- date1.month === date2.month &&
- date1.day === date2.day
- )
+ return date1.year === date2.year && date1.month === date2.month && date1.day === date2.day;
}
-export function getEventsForDate(events: EventDBModel[], day: Temporal.ZonedDateTime, nextDay: Temporal.ZonedDateTime): EventDBModel[] {
+export function getEventsForDate(
+ events: EventDBModel[],
+ day: Temporal.ZonedDateTime,
+ nextDay: Temporal.ZonedDateTime
+): EventDBModel[] {
return events.filter((event) => {
- return dateRangeOverlaps(day.toInstant().epochMilliseconds, nextDay.toInstant().epochMilliseconds, (new Date(event.startTime)).valueOf(), (new Date(event.endTime)).valueOf())
- })
+ return dateRangeOverlaps(
+ day.toInstant().epochMilliseconds,
+ nextDay.toInstant().epochMilliseconds,
+ new Date(event.startTime).valueOf(),
+ new Date(event.endTime).valueOf()
+ );
+ });
}
export type WeekSheetTimeSlot = {
- startMinute: number;
- endMinute: number;
- link: string;
-}
+ startMinute: number;
+ endMinute: number;
+ link: string;
+};
-export type URLRefHits = { name: string, hits: number }
+export type URLRefHits = { name: string; hits: number };
export interface DynamicURLModel extends RecordModel {
- owner: string,
- defaultRedirectTo: string
- timeZone: Temporal.TimeZoneLike
- weekSheet: WeekSheetTimeSlot[][],
- enableWeekSheet: boolean,
- overrideRedirectTo: string
- enableOverrideRedirect: boolean,
- disableURL: boolean,
- refs: URLRefHits[],
- created: string,
- updated: string,
+ owner: string;
+ defaultRedirectTo: string;
+ timeZone: Temporal.TimeZoneLike;
+ weekSheet: WeekSheetTimeSlot[][];
+ enableWeekSheet: boolean;
+ overrideRedirectTo: string;
+ enableOverrideRedirect: boolean;
+ disableURL: boolean;
+ refs: URLRefHits[];
+ created: string;
+ updated: string;
+ overrideExpiresIn: string;
+ overrideExpireInStr:
+ "never" | "15min" | "30min" | "1hour" | "2hour" | "4hour" | "8hour" | "16hour" | "set";
}
export const TIMEZONES = [
@@ -409,13 +460,13 @@ export const TIMEZONES = [
];
export interface AvailableIntegration {
- id: string;
- name: string;
- slug: string;
- description: string;
- icon: IntegrationModel["service"];
- docsUrl: string;
- comingSoon: boolean
+ id: string;
+ name: string;
+ slug: string;
+ description: string;
+ icon: IntegrationModel["service"];
+ docsUrl: string;
+ comingSoon: boolean;
}
export const availableIntegrations: AvailableIntegration[] = [
@@ -423,10 +474,11 @@ export const availableIntegrations: AvailableIntegration[] = [
id: "planningcenter",
name: "Planning Center",
slug: "planningcenter",
- description: "Sync events from Planning Center Calendar. Automatically imports services, events, and schedules.",
+ description:
+ "Sync events from Planning Center Calendar. Automatically imports services, events, and schedules.",
icon: "planningcenter",
docsUrl: "https://www.planningcenter.com/",
- comingSoon: false,
+ comingSoon: false
},
{
id: "twitter",
@@ -435,7 +487,7 @@ export const availableIntegrations: AvailableIntegration[] = [
description: "Integrate X Ai features like speech to text and translation.",
icon: "twitter",
docsUrl: "https://docs.x.com",
- comingSoon: false,
+ comingSoon: false
},
{
id: "breeze",
@@ -444,38 +496,45 @@ export const availableIntegrations: AvailableIntegration[] = [
description: "Connect with Breeze Church Management to sync your church events and calendar.",
icon: "breeze",
docsUrl: "https://www.breezechms.com/",
- comingSoon: true,
+ comingSoon: true
},
{
id: "googlecalendar",
name: "Google Calendar",
slug: "google-calendar",
- description: "Import events from Google Calendar. Supports multiple calendars and automatic sync.",
+ description:
+ "Import events from Google Calendar. Supports multiple calendars and automatic sync.",
icon: "google",
docsUrl: "https://calendar.google.com/",
- comingSoon: true,
+ comingSoon: true
}
];
export const errorTypes = {
- "signup_stripe_error": "Error using the stripe SDK.",
- "signup_oath_no_state": "Error connecting with Oath provider.",
- "signup_auth_method_get": "Failed to get Oath Method",
- "signup_auth_signup_fail": "Failed to signup with Oath",
- "signup_after_oath": "Failed to signup."
-}
+ signup_stripe_error: "Error using the stripe SDK.",
+ signup_oath_no_state: "Error connecting with Oath provider.",
+ signup_auth_method_get: "Failed to get Oath Method",
+ signup_auth_signup_fail: "Failed to signup with Oath",
+ signup_after_oath: "Failed to signup."
+};
export function hasKey(obj: T, key: PropertyKey): key is keyof T {
- return key in obj;
+ return key in obj;
}
-export function generateIframeCode(iframeLink: string, iframeTitle: string, id: string, infosectionsLink: string, removeScript?: boolean) {
+export function generateIframeCode(
+ iframeLink: string,
+ iframeTitle: string,
+ id: string,
+ infosectionsLink: string,
+ removeScript?: boolean
+) {
let text: string[] = [];
if (removeScript) {
text = [
- ''
- ]
+ ">"
+ ];
} else {
text = [
`',
- '"
+ ];
}
return text.join("\n");
-}
\ No newline at end of file
+}
diff --git a/frontend/src/lib/valibotSchemaHelpers.ts b/frontend/src/lib/valibotSchemaHelpers.ts
index bed1a07..3f50246 100644
--- a/frontend/src/lib/valibotSchemaHelpers.ts
+++ b/frontend/src/lib/valibotSchemaHelpers.ts
@@ -1,8 +1,8 @@
import * as v from "valibot";
export const EmailSchema = v.pipe(
- v.string(),
- v.nonEmpty('Please enter your email.'),
- v.email('The email is badly formatted.'),
- v.maxLength(30, 'Your email is too long.')
-);
\ No newline at end of file
+ v.string(),
+ v.nonEmpty("Please enter your email."),
+ v.email("The email is badly formatted."),
+ v.maxLength(30, "Your email is too long.")
+);
diff --git a/frontend/src/routes/(mainWebsite)/+layout.server.ts b/frontend/src/routes/(mainWebsite)/+layout.server.ts
index 1fcc361..583f6b3 100644
--- a/frontend/src/routes/(mainWebsite)/+layout.server.ts
+++ b/frontend/src/routes/(mainWebsite)/+layout.server.ts
@@ -1,32 +1,32 @@
-import type { UserModel } from '@/utils';
-import { redirect } from '@sveltejs/kit';
+import type { UserModel } from "@/utils";
+import { redirect } from "@sveltejs/kit";
import { config } from "dotenv";
config();
export async function load({ locals, url }) {
- if (locals.user) {
- try {
- const user: UserModel = await locals.pb.collection("users").getOne(locals.user.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
-
- return {
- user,
- avatar: locals.pb.files.getURL(user, user.avatar),
- pathname: url.pathname,
- stripeCustomerPortal: process.env["STRIPE_CUSTOMER_PORTAL_LINK"]!
- }
- } catch (err) {
- console.log(err);
+ if (locals.user) {
+ try {
+ const user: UserModel = await locals.pb.collection("users").getOne(locals.user.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
- throw redirect(307, "/logout");
- }
- }
- return {
- pathname: url.pathname,
- stripeCustomerPortal: process.env["STRIPE_CUSTOMER_PORTAL_LINK"]!
- }
-}
\ No newline at end of file
+ return {
+ user,
+ avatar: locals.pb.files.getURL(user, user.avatar),
+ pathname: url.pathname,
+ stripeCustomerPortal: process.env["STRIPE_CUSTOMER_PORTAL_LINK"]!
+ };
+ } catch (err) {
+ console.log(err);
+
+ throw redirect(307, "/logout");
+ }
+ }
+ return {
+ pathname: url.pathname,
+ stripeCustomerPortal: process.env["STRIPE_CUSTOMER_PORTAL_LINK"]!
+ };
+}
diff --git a/frontend/src/routes/(mainWebsite)/+page.server.ts b/frontend/src/routes/(mainWebsite)/+page.server.ts
index 6ebd1d1..10eedb8 100644
--- a/frontend/src/routes/(mainWebsite)/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/+page.server.ts
@@ -1,3 +1,3 @@
export async function load({ parent }) {
- await parent();
-}
\ No newline at end of file
+ await parent();
+}
diff --git a/frontend/src/routes/(mainWebsite)/+page.svelte b/frontend/src/routes/(mainWebsite)/+page.svelte
index ed5bfa9..550f944 100644
--- a/frontend/src/routes/(mainWebsite)/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/+page.svelte
@@ -1,35 +1,37 @@
- InfoSections
+ InfoSections
-
+
-
\ No newline at end of file
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/+error.svelte b/frontend/src/routes/(mainWebsite)/dashboard/+error.svelte
index d049933..be2490f 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/+error.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/+error.svelte
@@ -1,38 +1,51 @@
- {page.status} {page.error?.message}
+ {page.status} {page.error?.message}
-
-
+
+
-
{page.status} {page.error?.message}
+
{page.status} {page.error?.message}
-
- {#if page.status === 404}
- We couldn't find the page you're looking for. This might be due to an invalid link.
- {:else}
- An error has occured. Please wait and try again. If the error keeps happening, contact our support team.
- {/if}
-
+
+ {#if page.status === 404}
+ We couldn't find the page you're looking for. This might be due to an invalid link.
+ {:else}
+ An error has occured. Please wait and try again. If the error keeps happening, contact our
+ support team.
+ {/if}
+
-
-
- Try Again
-
+
+
+ Try Again
+
-
-
-
-
\ No newline at end of file
+
+ Need help?
Contact support
+
+
+
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/+layout.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/+layout.server.ts
index d577be0..2b40c77 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/+layout.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/+layout.server.ts
@@ -1,19 +1,19 @@
-import { redirect } from '@sveltejs/kit';
+import { redirect } from "@sveltejs/kit";
import { config } from "dotenv";
config();
export async function load({ parent }) {
- const data = await parent();
+ const data = await parent();
- if (!data.user) {
- return redirect(307, "/");
- }
+ if (!data.user) {
+ return redirect(307, "/");
+ }
- return {
- ...data,
- pb_url: process.env["PB_URL"]!,
- stripeSubscriptionUrl: data.user.subscriptionURL,
- stripeTrialSubscriptionUrl: data.user.freeTrialURL
- }
-}
\ No newline at end of file
+ return {
+ ...data,
+ pb_url: process.env["PB_URL"]!,
+ stripeSubscriptionUrl: data.user.subscriptionURL,
+ stripeTrialSubscriptionUrl: data.user.freeTrialURL
+ };
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/+layout.svelte b/frontend/src/routes/(mainWebsite)/dashboard/+layout.svelte
index 9684115..d6abf37 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/+layout.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/+layout.svelte
@@ -1,29 +1,32 @@
-
+
-
+
{#if navigating.complete !== null}
@@ -50,4 +57,4 @@
max-height: calc(100vh - 64px);
overflow-y: auto;
}
-
\ No newline at end of file
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/+page.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/+page.server.ts
index 81c607b..10eedb8 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/+page.server.ts
@@ -1,3 +1,3 @@
export async function load({ parent }) {
- await parent();
-}
\ No newline at end of file
+ await parent();
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/+page.svelte b/frontend/src/routes/(mainWebsite)/dashboard/+page.svelte
index aa7030a..2aa9cd3 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/+page.svelte
@@ -1,71 +1,67 @@
- Dashboard | InfoSections
+ Dashboard | InfoSections
-
-
-
- Hello, {data.user.name}!
-
-
- {"Here's an overview of your events and activity."}
-
-
+
+
+
+ Hello, {data.user.name}!
+
+
+ {"Here's an overview of your events and activity."}
+
+
-
+
-
-
-
\ No newline at end of file
+
+
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/backend.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/backend.remote.ts
index 5d5cfce..4e6e50a 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/backend.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/backend.remote.ts
@@ -1,5 +1,11 @@
//This file can fetch all the needed data from the pocketbase instance. Stuff like calendars, image feeds, etc.
-import type { CustomImageIFeedDBModel, DynamicURLModel, EventListDBModel, ImageFeedDBModel, IntegrationModel } from "@/utils";
+import type {
+ CustomImageIFeedDBModel,
+ DynamicURLModel,
+ EventListDBModel,
+ ImageFeedDBModel,
+ IntegrationModel
+} from "@/utils";
import { getRequestEvent, query } from "$app/server";
import type { CalendarDBModel } from "@/cal.utils";
import { redirect } from "@sveltejs/kit";
@@ -9,203 +15,250 @@ import * as v from "valibot";
config();
export const getMyIntegrations = query(async () => {
- const { locals } = getRequestEvent();
- let myIntegrations: IntegrationModel[] = [];
-
- if (!locals.user) return myIntegrations;
-
- try {
- myIntegrations = await locals.pb.collection("integration").getFullList({
- filter: `owner="${locals.user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Error when fetching integrations, ", err);
- }
-
- return myIntegrations;
+ const { locals } = getRequestEvent();
+ let myIntegrations: IntegrationModel[] = [];
+
+ if (!locals.user) return myIntegrations;
+
+ try {
+ myIntegrations = await locals.pb.collection("integration").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Error when fetching integrations, ", err);
+ }
+
+ return myIntegrations;
});
export const getMyCalendars = query(async () => {
- const { locals } = getRequestEvent();
- let calendars: CalendarDBModel[] = [];
-
- if (!locals.user) return calendars;
-
- try {
- calendars = await locals.pb.collection('calendars').getFullList({
- filter: `owner="${locals.user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to fetch calendars.", err);
- }
-
- return calendars;
+ const { locals } = getRequestEvent();
+ let calendars: CalendarDBModel[] = [];
+
+ if (!locals.user) return calendars;
+
+ try {
+ calendars = await locals.pb.collection("calendars").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to fetch calendars.", err);
+ }
+
+ return calendars;
});
/**
* This gets a calendar by the database id, it also checks if the locals.user is the owner of this calendar. If not, the user gets redirected.
*/
export const getCalendarById = query(v.string(), async (id) => {
- const { locals } = getRequestEvent();
- let calendar: CalendarDBModel | undefined;
+ const { locals } = getRequestEvent();
+ let calendar: CalendarDBModel | undefined;
- if (!locals.user) return redirect(303, "/dashboard/calendars");
+ if (!locals.user) return redirect(303, "/dashboard/calendars");
- try {
- calendar = await locals.pb.collection('calendars').getOne(id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to fetch calendars.", err);
- return redirect(303, "/dashboard/calendars");
- }
+ try {
+ calendar = await locals.pb.collection("calendars").getOne(id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to fetch calendars.", err);
+ return redirect(303, "/dashboard/calendars");
+ }
- if (!calendar) return redirect(303, "/dashboard/calendars");
+ if (!calendar) return redirect(303, "/dashboard/calendars");
- if (calendar.owner !== locals.user.id) return redirect(303, "/dashboard/calendars");
+ if (calendar.owner !== locals.user.id) return redirect(303, "/dashboard/calendars");
- return calendar;
+ return calendar;
});
export const getMyImageFeeds = query(async () => {
- const { locals } = getRequestEvent();
- let imageFeeds: ImageFeedDBModel[] = [];
-
- if (!locals.user) return imageFeeds;
-
- try {
- imageFeeds = await locals.pb.collection('imageFeeds').getFullList({
- filter: `owner="${locals.user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to fetch image feeds.", err);
- }
-
- return imageFeeds;
+ const { locals } = getRequestEvent();
+ let imageFeeds: ImageFeedDBModel[] = [];
+
+ if (!locals.user) return imageFeeds;
+
+ try {
+ imageFeeds = await locals.pb.collection("imageFeeds").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to fetch image feeds.", err);
+ }
+
+ return imageFeeds;
});
/**
* This gets a image feed by the database id, it also checks if the locals.user is the owner of this feed. If not, the user gets redirected.
*/
export const getImageFeedById = query(v.string(), async (id) => {
- const { locals } = getRequestEvent();
- let imageFeed: ImageFeedDBModel | undefined;
+ const { locals } = getRequestEvent();
+ let imageFeed: ImageFeedDBModel | undefined;
- if (!locals.user) return redirect(303, "/dashboard/image-feeds");
+ if (!locals.user) return redirect(303, "/dashboard/image-feeds");
- try {
- imageFeed = await locals.pb.collection('imageFeeds').getOne(id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to fetch image feeds.", err);
- return redirect(303, "/dashboard/image-feeds");
- }
+ try {
+ imageFeed = await locals.pb.collection("imageFeeds").getOne(id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to fetch image feeds.", err);
+ return redirect(303, "/dashboard/image-feeds");
+ }
- if (!imageFeed) return redirect(303, "/dashboard/image-feeds");
+ if (!imageFeed) return redirect(303, "/dashboard/image-feeds");
- if (imageFeed.owner !== locals.user.id) return redirect(303, "/dashboard/image-feeds");
+ if (imageFeed.owner !== locals.user.id) return redirect(303, "/dashboard/image-feeds");
- return imageFeed;
+ return imageFeed;
});
export const getCustomImagesForIFeed = query(v.string(), async (id) => {
- const { locals } = getRequestEvent();
- let customImages: CustomImageIFeedDBModel[] = [];
-
- if (!locals.user) return customImages;
-
- try {
- customImages = await locals.pb.collection('customImageIfeed').getFullList({
- filter: `imageFeed ~ "${id}" && owner = "${locals.user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to fetch image feed custom images.", err);
- return redirect(303, "/dashboard/image-feeds");
- }
-
- return customImages;
+ const { locals } = getRequestEvent();
+ let customImages: CustomImageIFeedDBModel[] = [];
+
+ if (!locals.user) return customImages;
+
+ try {
+ customImages = await locals.pb.collection("customImageIfeed").getFullList({
+ filter: `imageFeed ~ "${id}" && owner = "${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to fetch image feed custom images.", err);
+ return redirect(303, "/dashboard/image-feeds");
+ }
+
+ return customImages;
});
export const getMyEventLists = query(async () => {
- const { locals } = getRequestEvent();
- let eventLists: EventListDBModel[] = [];
-
- if (!locals.user) return eventLists;
-
- try {
- eventLists = await locals.pb.collection('eventLists').getFullList({
- filter: `owner="${locals.user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to event lists.", err);
- }
-
- return eventLists;
+ const { locals } = getRequestEvent();
+ let eventLists: EventListDBModel[] = [];
+
+ if (!locals.user) return eventLists;
+
+ try {
+ eventLists = await locals.pb.collection("eventLists").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to event lists.", err);
+ }
+
+ return eventLists;
});
/**
* This gets a event list by the database id, it also checks if the locals.user is the owner of this list. If not, the user gets redirected.
-*/
+ */
export const getEventListById = query(v.string(), async (id) => {
- const { locals } = getRequestEvent();
- let eventList: EventListDBModel | undefined;
-
- if (!locals.user) return redirect(303, "/dashboard/event-lists");
-
- try {
- eventList = await locals.pb.collection('eventLists').getOne(id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to fetch event list.", err);
- return redirect(303, "/dashboard/event-lists");
- }
-
- if (!eventList) return redirect(303, "/dashboard/event-lists");
-
- if (eventList.owner !== locals.user.id) return redirect(303, "/dashboard/event-lists");
-
- return eventList;
+ const { locals } = getRequestEvent();
+ let eventList: EventListDBModel | undefined;
+
+ if (!locals.user) return redirect(303, "/dashboard/event-lists");
+
+ try {
+ eventList = await locals.pb.collection("eventLists").getOne(id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to fetch event list.", err);
+ return redirect(303, "/dashboard/event-lists");
+ }
+
+ if (!eventList) return redirect(303, "/dashboard/event-lists");
+
+ if (eventList.owner !== locals.user.id) return redirect(303, "/dashboard/event-lists");
+
+ return eventList;
});
export const getMyDynamicURLS = query(async () => {
- const { locals } = getRequestEvent();
- let dynamic_urls: DynamicURLModel[] = [];
-
- if (!locals.user) return dynamic_urls;
-
- try {
- dynamic_urls = await locals.pb.collection('dynamic_url').getFullList({
- filter: `owner="${locals.user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to dynamic urls.", err);
- }
-
- return dynamic_urls;
-});
\ No newline at end of file
+ const { locals } = getRequestEvent();
+ let dynamic_urls: DynamicURLModel[] = [];
+
+ if (!locals.user) return dynamic_urls;
+
+ try {
+ dynamic_urls = await locals.pb.collection("dynamic_url").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to dynamic urls.", err);
+ }
+
+ let somethingChanged = false;
+ const batch = locals.pb.createBatch();
+ const now = new Date().getTime();
+ for (let i = 0; i < dynamic_urls.length; i++) {
+ const url = dynamic_urls[i];
+ if (!url) continue;
+ if (url.overrideExpireInStr !== "set") continue;
+ if (new Date(url.overrideExpiresIn).getTime() > now) continue;
+ batch.collection("dynamic_url").update(
+ url.id,
+ {
+ overrideExpiresIn: "",
+ overrideExpireInStr: "never",
+ enableOverrideRedirect: false
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ somethingChanged = true;
+ }
+
+ if (somethingChanged) {
+ try {
+ const result = await batch.send({
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ console.log({ bod: result[0]?.body });
+ } catch (err) {
+ console.log("Error updating dynamic url override expire date resets,", err);
+ }
+ try {
+ dynamic_urls = await locals.pb.collection("dynamic_url").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to dynamic urls.", err);
+ }
+ }
+
+ return dynamic_urls;
+});
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/calendars/+page.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/calendars/+page.server.ts
index 81c607b..10eedb8 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/calendars/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/calendars/+page.server.ts
@@ -1,3 +1,3 @@
export async function load({ parent }) {
- await parent();
-}
\ No newline at end of file
+ await parent();
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/calendars/+page.svelte b/frontend/src/routes/(mainWebsite)/dashboard/calendars/+page.svelte
index 79fd4bf..0ec52e2 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/calendars/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/calendars/+page.svelte
@@ -1,253 +1,276 @@
- My Calendars | InfoSections
+ My Calendars | InfoSections
-
-
-
My Calendars
-
Customisable calendars that can be accessed anywhere by anyone (Unless it's password protected.)
-
-
-
-
-
- New Calendar
-
-
-
- Create New Calendar
- You can change more settings after creating the calendar.
-
-
-
-
- Calendar Name
-
-
-
-
- Description
-
-
-
-
-
- Password Protection
-
-
Require a password to access this calendar
-
-
-
-
- {#if newCalendarPasswordEnabled}
-
- Calendar Password
-
-
- {/if}
-
-
-
- newCalendarDialogOpen = false}>
- Cancel
-
-
- {#if creatingCalendar}
-
- Creating Calendar...
- {:else}
- Create Calendar
- {/if}
-
-
-
-
-
-
-
- {#if (await getMyCalendars()).length === 0}
-
No Calendars Yet.
- {/if}
- {#each await getMyCalendars() as calendar (`calendarlist${calendar.id}`)}
-
-
-
-
-
-
-
-
-
-
- {calendar.name}
- {#if calendar.passwordEnabled}
-
- •
-
-
-
-
-
- Password Protected
-
-
-
- {/if}
-
-
/cal/{calendar.id}
-
-
-
-
-
-
-
-
- copyCalLinkToClipboard(calendar.id)}
- >
-
- Copy Link
-
-
- {deleteCalendarId = calendar.id}}>
-
- Delete
-
-
-
-
-
-
-
- {calendar.description}
-
-
-
-
- {calendar.visits} Visit{calendar.visits === 1 ? "" : "s"}
-
-
-
- View Details
-
-
-
-
- {/each}
-
+
+
+
My Calendars
+
+ Customisable calendars that can be accessed anywhere by anyone (Unless it's password
+ protected.)
+
+
+
+
+
+
+ New Calendar
+
+
+
+ Create New Calendar
+ You can change more settings after creating the calendar.
+
+
+
+
+ Calendar URL path
+
+
+
+
+ Calendar Name
+
+
+
+
+ Description
+
+
+
+
+
Password Protection
+
+ Require a password to access this calendar
+
+
+
+
+
+ {#if newCalendarPasswordEnabled}
+
+ Calendar Password
+
+
+ {/if}
+
+
+
+ (newCalendarDialogOpen = false)}>Cancel
+
+ {#if creatingCalendar}
+
+ Creating Calendar...
+ {:else}
+ Create Calendar
+ {/if}
+
+
+
+
+
+
+
+ {#if (await getMyCalendars()).length === 0}
+
No Calendars Yet.
+ {/if}
+ {#each await getMyCalendars() as calendar (`calendarlist${calendar.id}`)}
+
+
+
+
+
+
+
+
+
+
+ {calendar.name}
+ {#if calendar.passwordEnabled}
+
+ •
+
+
+
+
+
+ Password Protected
+
+
+
+ {/if}
+
+
/cal/{calendar.publicId}
+
+
+
+
+
+
+
+
+ copyCalLinkToClipboard(calendar.id)}>
+
+ Copy Link
+
+
+ {
+ deleteCalendarId = calendar.id;
+ }}
+ >
+
+ Delete
+
+
+
+
+
+
+
+ {calendar.description}
+
+
+
+
+ {calendar.visits} Visit{calendar.visits === 1 ? "" : "s"}
+
+
+
+ View Details
+
+
+
+
+ {/each}
+
- deleteCalendarId = null}>
-
-
- Are you sure absolutely sure?
-
- This action cannot be undone. This will permanently delete this calendar from our servers. All links or image feeds relying on this calendar will not work.
-
-
- Confirm Delete
-
-
-
-
\ No newline at end of file
+ (deleteCalendarId = null)}>
+
+
+ Are you sure absolutely sure?
+
+ This action cannot be undone. This will permanently delete this calendar from our servers.
+ All links or image feeds relying on this calendar will not work.
+
+
+ Confirm Delete
+
+
+
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/calendars/[slug]/+page.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/calendars/[slug]/+page.server.ts
index 7a8474e..9af2a5e 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/calendars/[slug]/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/calendars/[slug]/+page.server.ts
@@ -1,8 +1,8 @@
export async function load({ params, parent }) {
- await parent();
- let slug = params.slug;
+ await parent();
+ let slug = params.slug;
- return {
- selectedCalendarId: slug
- }
-}
\ No newline at end of file
+ return {
+ selectedCalendarId: slug
+ };
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/calendars/[slug]/+page.svelte b/frontend/src/routes/(mainWebsite)/dashboard/calendars/[slug]/+page.svelte
index bf95647..7bda2b2 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/calendars/[slug]/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/calendars/[slug]/+page.svelte
@@ -1,159 +1,184 @@
- {selectedCalendar.name} | InfoSections
+ {selectedCalendar.name} | InfoSections
-
-
-
-
-
-
{selectedCalendar.name} | Calendar Details
-
Manage your calendar settings and filters.
-
-
-
-
-
{
- let savingChanges = toast.loading("Saving Changes.", { duration: Number.POSITIVE_INFINITY });
- try {
- await submit();
- form.reset();
-
- clearFileInput(document.getElementById("imageUploaderCalendar"));
- uploadNewAvatar = null;
- toast.dismiss(savingChanges);
-
- toast.success("Saved Changes");
- } catch (err) {
- console.log(err);
- toast.dismiss(savingChanges);
- toast.error("An error occured.");
- }
- })} class="lg:col-span-2 space-y-6" enctype="multipart/form-data">
- Update Calendar
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
{selectedCalendar.name} | Calendar Details
+
Manage your calendar settings and filters.
+
+
+
+
+
{
+ let savingChanges = toast.loading("Saving Changes.", {
+ duration: Number.POSITIVE_INFINITY
+ });
+ try {
+ await submit();
+ toast.dismiss(savingChanges);
+
+ if (!updateCalendarForm.fields.allIssues()) {
+ form.reset();
+
+ clearFileInput(document.getElementById("imageUploaderCalendar"));
+ uploadNewAvatar = null;
+ toast.success("Saved Changes");
+ } else {
+ toast.error("Something went wrong!");
+ }
+ } catch (err) {
+ console.log(err);
+ toast.dismiss(savingChanges);
+ toast.error("An error occured.");
+ }
+ })}
+ class="lg:col-span-2 space-y-6"
+ enctype="multipart/form-data"
+ >
+ {#if selectedCalendar.id.length > 0}
+
+ {/if}
+ Update Calendar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
+ .stickySidebar {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ }
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/calendars/calendarActions.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/calendars/calendarActions.remote.ts
index c78bb75..4cd752b 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/calendars/calendarActions.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/calendars/calendarActions.remote.ts
@@ -1,4 +1,8 @@
-import { defaultCalendarCustomizations, defaultCalendarFilters, type CalendarDBModel } from "@/cal.utils";
+import {
+ defaultCalendarCustomizations,
+ defaultCalendarFilters,
+ type CalendarDBModel
+} from "@/cal.utils";
import { getCalendarById, getMyCalendars } from "../backend.remote";
import { command, form, getRequestEvent } from "$app/server";
import { invalid, redirect } from "@sveltejs/kit";
@@ -7,186 +11,233 @@ import * as v from "valibot";
config();
+async function calPublicIdInUse(locals: App.Locals, pubID: string) {
+ try {
+ await locals.pb.collection("calendars").getFirstListItem(`publicId="${pubID}"`, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ return true;
+ } catch (err) {
+ return false;
+ }
+}
+
const CreateCalendarSchema = v.object({
- name: v.pipe(v.string(), v.nonEmpty("A name for the calendar is required.")),
- description: v.pipe(v.string(), v.nonEmpty("A description for the calendar is required.")),
- enablePassword: v.boolean(),
- newPassword: v.string()
+ publicId: v.pipe(
+ v.string(),
+ v.regex(/^[a-zA-Z0-9]+$/, "No spaces or special characters allowed.")
+ ),
+ name: v.pipe(v.string(), v.nonEmpty("A name for the calendar is required.")),
+ description: v.pipe(v.string(), v.nonEmpty("A description for the calendar is required.")),
+ enablePassword: v.boolean(),
+ newPassword: v.string()
});
export const createCalendarCommand = command(CreateCalendarSchema, async (newCalendar) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const calendars = await getMyCalendars();
-
- if (calendars.length > 0 && locals.user.accessLevel === "none") {
- return {
- error: true,
- msg: "Exceed the amount of calendars on the free plan."
- }
- }
-
- try {
- let data: Partial = {
- "name": newCalendar.name,
- "description": newCalendar.description,
- "passwordEnabled": newCalendar.enablePassword,
- "owner": locals.user.id,
- "displaySettings": defaultCalendarCustomizations,
- "filters": defaultCalendarFilters
- };
-
- if (newCalendar.enablePassword) {
- data["password"] = newCalendar.newPassword;
- }
-
- await locals.pb.collection('calendars').create(data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: "Internal Server Error."
- }
- }
-
- getMyCalendars().refresh();
- return {
- error: false,
- msg: "Successfully created calendar."
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const calendars = await getMyCalendars();
+
+ if (calendars.length > 0 && locals.user.accessLevel === "none") {
+ return {
+ error: true,
+ msg: "Exceed the amount of calendars on the free plan."
+ };
+ }
+
+ if (await calPublicIdInUse(locals, newCalendar.publicId)) {
+ return {
+ error: true,
+ msg: "Calendar URL path already taken."
+ };
+ }
+
+ try {
+ let data: Partial = {
+ publicId: newCalendar.publicId,
+ name: newCalendar.name,
+ description: newCalendar.description,
+ passwordEnabled: newCalendar.enablePassword,
+ owner: locals.user.id,
+ displaySettings: defaultCalendarCustomizations,
+ filters: defaultCalendarFilters
+ };
+
+ if (newCalendar.enablePassword) {
+ data["password"] = newCalendar.newPassword;
+ }
+
+ await locals.pb.collection("calendars").create(data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: "Internal Server Error."
+ };
+ }
+
+ getMyCalendars().refresh();
+ return {
+ error: false,
+ msg: "Successfully created calendar."
+ };
});
const UpdateCalendarSchema = v.object({
- id: v.string(),
- name: v.string(),
- description: v.string(),
- enablePassword: v.optional(v.boolean(), false),
- newPassword: v.optional(v.string()),
- avatarLink: v.optional(v.string()),
- newAvatar: v.optional(v.any()),
- passwordScreenMessage: v.optional(v.string()),
- displaySettings: v.object({
- viewType: v.picklist(["3day", "week", "month"]),
- useAMPM: v.optional(v.boolean(), false),
- showResourcePathname: v.optional(v.boolean(), false),
- onlyShowLocationTitle: v.optional(v.boolean(), false),
- showLocation: v.optional(v.boolean(), false),
- showResources: v.optional(v.boolean(), false),
- showRooms: v.optional(v.boolean(), false),
- showDescription: v.optional(v.boolean(), false)
- }),
- filters: v.object({
- onlyShowFeatured: v.optional(v.boolean(), false),
- hideUnpublished: v.optional(v.boolean(), false),
- resourceFilterType: v.picklist(["allow", "block"]),
- allowResources: v.optional(v.array(v.string()), []),
- blockResources: v.optional(v.array(v.string()), []),
- enableResourceFiltering: v.optional(v.boolean(), false),
- tagFilterType: v.picklist(["allow", "block"]),
- allowTags: v.optional(v.array(v.string()), []),
- blockTags: v.optional(v.array(v.string()), []),
- enableTagFiltering: v.optional(v.boolean(), false)
- })
+ id: v.string(),
+ publicId: v.pipe(
+ v.string(),
+ v.regex(/^[a-zA-Z0-9]+$/, "No spaces or special characters allowed.")
+ ),
+ name: v.string(),
+ description: v.string(),
+ enablePassword: v.optional(v.boolean(), false),
+ newPassword: v.optional(v.string()),
+ avatarLink: v.optional(v.string()),
+ newAvatar: v.optional(v.any()),
+ passwordScreenMessage: v.optional(v.string()),
+ displaySettings: v.object({
+ viewType: v.picklist(["3day", "week", "month"]),
+ useAMPM: v.optional(v.boolean(), false),
+ showResourcePathname: v.optional(v.boolean(), false),
+ onlyShowLocationTitle: v.optional(v.boolean(), false),
+ showLocation: v.optional(v.boolean(), false),
+ showResources: v.optional(v.boolean(), false),
+ showRooms: v.optional(v.boolean(), false),
+ showDescription: v.optional(v.boolean(), false)
+ }),
+ filters: v.object({
+ onlyShowFeatured: v.optional(v.boolean(), false),
+ hideUnpublished: v.optional(v.boolean(), false),
+ resourceFilterType: v.picklist(["allow", "block"]),
+ allowResources: v.optional(v.array(v.string()), []),
+ blockResources: v.optional(v.array(v.string()), []),
+ enableResourceFiltering: v.optional(v.boolean(), false),
+ tagFilterType: v.picklist(["allow", "block"]),
+ allowTags: v.optional(v.array(v.string()), []),
+ blockTags: v.optional(v.array(v.string()), []),
+ enableTagFiltering: v.optional(v.boolean(), false)
+ })
});
export const updateCalendarForm = form(UpdateCalendarSchema, async (updatedCalendar, issue) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return redirect(303, "/");
- }
-
- const calendar = await getCalendarById(updatedCalendar.id);
-
- try {
- let data: Partial = {
- "name": updatedCalendar.name,
- "description": updatedCalendar.description,
- "passwordEnabled": updatedCalendar.enablePassword,
- "displaySettings": updatedCalendar.displaySettings,
- "filters": updatedCalendar.filters
- };
-
- if (updatedCalendar.newPassword && updatedCalendar.newPassword.length > 0 && updatedCalendar.enablePassword) {
- data["password"] = updatedCalendar.newPassword;
- }
-
- if (updatedCalendar.passwordScreenMessage && updatedCalendar.passwordScreenMessage.length > 0 && updatedCalendar.enablePassword) {
- data["passwordScreenMessage"] = updatedCalendar.passwordScreenMessage;
- }
-
- if (!updatedCalendar.avatarLink && updatedCalendar.newAvatar) {
- const newAvatar = updatedCalendar.newAvatar as File;
- const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, { type: newAvatar.type });
- data["logo"] = file;
- }
-
- if (!updatedCalendar.avatarLink && !updatedCalendar.newAvatar) {
- data["logo"] = "";
- }
-
- await locals.pb.collection('calendars').update(calendar.id, data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return invalid(issue.id("Failed to update calendar."));
- }
-
- getMyCalendars().refresh();
- getCalendarById(calendar.id).refresh();
- return {
- error: false,
- msg: "Successfully updated calendar."
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return redirect(303, "/");
+ }
+
+ const calendar = await getCalendarById(updatedCalendar.id);
+
+ if (
+ calendar.publicId !== updatedCalendar.publicId &&
+ (await calPublicIdInUse(locals, updatedCalendar.publicId))
+ ) {
+ return invalid(issue.publicId("Calendar URL path already taken."));
+ }
+
+ try {
+ let data: Partial = {
+ publicId: updatedCalendar.publicId,
+ name: updatedCalendar.name,
+ description: updatedCalendar.description,
+ passwordEnabled: updatedCalendar.enablePassword,
+ displaySettings: updatedCalendar.displaySettings,
+ filters: updatedCalendar.filters
+ };
+
+ if (
+ updatedCalendar.newPassword &&
+ updatedCalendar.newPassword.length > 0 &&
+ updatedCalendar.enablePassword
+ ) {
+ data["password"] = updatedCalendar.newPassword;
+ }
+
+ if (
+ updatedCalendar.passwordScreenMessage &&
+ updatedCalendar.passwordScreenMessage.length > 0 &&
+ updatedCalendar.enablePassword
+ ) {
+ data["passwordScreenMessage"] = updatedCalendar.passwordScreenMessage;
+ }
+
+ if (!updatedCalendar.avatarLink && updatedCalendar.newAvatar) {
+ const newAvatar = updatedCalendar.newAvatar as File;
+ const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, {
+ type: newAvatar.type
+ });
+ data["logo"] = file;
+ }
+
+ if (!updatedCalendar.avatarLink && !updatedCalendar.newAvatar) {
+ data["logo"] = "";
+ }
+
+ await locals.pb.collection("calendars").update(calendar.id, data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return invalid(issue.id("Failed to update calendar."));
+ }
+
+ getMyCalendars().refresh();
+ getCalendarById(calendar.id).refresh();
+ return {
+ error: false,
+ msg: "Successfully updated calendar."
+ };
});
export const deleteCalendarCommand = command(v.string(), async (id) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const calendar = await getCalendarById(id);
-
- try {
- await locals.pb.collection('calendars').delete(calendar.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: `Internal Server Error.`
- }
- }
-
- getMyCalendars().refresh();
- // getMyCalendarById(calendar.id).refresh();
- return {
- error: false,
- msg: "Successfully deleted calendar."
- }
-});
\ No newline at end of file
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const calendar = await getCalendarById(id);
+
+ try {
+ await locals.pb.collection("calendars").delete(calendar.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: `Internal Server Error.`
+ };
+ }
+
+ getMyCalendars().refresh();
+ // getMyCalendarById(calendar.id).refresh();
+ return {
+ error: false,
+ msg: "Successfully deleted calendar."
+ };
+});
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/+page.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/+page.server.ts
index 8d8c959..086ce30 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/+page.server.ts
@@ -4,12 +4,12 @@ import { config } from "dotenv";
config();
export async function load({ parent, locals }) {
- await parent();
- if (!locals.user) {
- return redirect(307, "/");
- }
+ await parent();
+ if (!locals.user) {
+ return redirect(307, "/");
+ }
- return {
- websiteURL: process.env["VITE_WEBSITE_URL"]!
- }
-}
\ No newline at end of file
+ return {
+ websiteURL: process.env["VITE_WEBSITE_URL"]!
+ };
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/+page.svelte b/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/+page.svelte
index 301b75f..ad896fb 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/+page.svelte
@@ -1,218 +1,213 @@
- My Dynamic URLs | InfoSections
+ My Dynamic URLs | InfoSections
-
-
-
-
My Dynamic URLs
-
- Create smart redirects that change based on time of day and day of week.
-
-
-
-
- New Dynamic URL
-
-
-
-
-
- Total URLs
- {(await getMyDynamicURLS()).length}
-
-
- Active
-
- {(await getMyDynamicURLS()).filter((u) => !u.disableURL).length}
-
-
-
- Total Hits
-
- {(await getMyDynamicURLS()).reduce((sum, u) => sum + getTotalHits(u), 0).toLocaleString()}
-
-
-
- Time Slots
-
- {(await getMyDynamicURLS()).reduce((sum, u) => sum + getTimeSlotCount(u), 0)}
-
-
-
-
-
- {#each await getMyDynamicURLS() as dynamic_url (`aDynamicURL${dynamic_url.id}`)}
-
- {/each}
-
- {#if (await getMyDynamicURLS()).length === 0}
-
-
- No dynamic URLs yet
-
- Create your first smart redirect to get started.
-
-
-
- Create Dynamic URL
-
-
- {/if}
-
+
+
+
My Dynamic URLs
+
+ Create smart redirects that change based on time of day and day of week.
+
+
+
+
+ New Dynamic URL
+
+
+
+
+
+ Total URLs
+ {(await getMyDynamicURLS()).length}
+
+
+ Active
+
+ {(await getMyDynamicURLS()).filter((u) => !u.disableURL).length}
+
+
+
+ Total Hits
+
+ {(await getMyDynamicURLS()).reduce((sum, u) => sum + getTotalHits(u), 0).toLocaleString()}
+
+
+
+ Time Slots
+
+ {(await getMyDynamicURLS()).reduce((sum, u) => sum + getTimeSlotCount(u), 0)}
+
+
+
+
+
+ {#each await getMyDynamicURLS() as dynamic_url (`aDynamicURL${dynamic_url.id}`)}
+
+ {/each}
+
+ {#if (await getMyDynamicURLS()).length === 0}
+
+
+ No dynamic URLs yet
+
+ Create your first smart redirect to get started.
+
+
+
+ Create Dynamic URL
+
+
+ {/if}
+
-
-
- Create New Dynamic URL
- You can change more settings after creating the URL.
-
-
-
-
- URL ID
-
-
-
-
-
Default Redirect URL
-
-
Where visitors go when no schedule or override matches.
-
-
-
-
Timezone
-
-
- {timeZone}
-
-
- {#each TIMEZONES as tz (`aTZ${tz}`)}
-
- {tz.replace(/_/g, " ")}
-
- {/each}
-
-
-
Schedule times are evaluated in this timezone.
-
-
-
-
- newDynamicURLOpen = false}>
- Cancel
-
-
- {#if creatingDynamicURL}
-
- Creating Dynamic URL...
- {:else}
- Create Dynamic URL
- {/if}
-
-
-
-
\ No newline at end of file
+
+
+ Create New Dynamic URL
+ You can change more settings after creating the URL.
+
+
+
+
+ URL ID
+
+
+
+
+
Default Redirect URL
+
+
+ Where visitors go when no schedule or override matches.
+
+
+
+
+
Timezone
+
+
+ {timeZone}
+
+
+ {#each TIMEZONES as tz (`aTZ${tz}`)}
+
+ {tz.replace(/_/g, " ")}
+
+ {/each}
+
+
+
Schedule times are evaluated in this timezone.
+
+
+
+
+ (newDynamicURLOpen = false)}>Cancel
+
+ {#if creatingDynamicURL}
+
+ Creating Dynamic URL...
+ {:else}
+ Create Dynamic URL
+ {/if}
+
+
+
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/data.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/data.remote.ts
index 76ca62f..2c88707 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/data.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/dynamic-urls/data.remote.ts
@@ -7,272 +7,289 @@ import * as v from "valibot";
config();
const CreateDynamicURLSchema = v.object({
- id: v.string(),
- defaultRedirectTo: v.pipe(v.string(), v.url()),
- timeZone: v.string()
+ id: v.string(),
+ defaultRedirectTo: v.pipe(v.string(), v.url()),
+ timeZone: v.string()
});
export const createDynamicURLCommand = command(CreateDynamicURLSchema, async (newURLData) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- let dynamicURL: DynamicURLModel | null = null;
- try {
- dynamicURL = await locals.pb.collection("dynamic_url").getOne(newURLData.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch(_err) {
- //All Good
- }
-
- if (dynamicURL) {
- return {
- error: true,
- msg: `URL ID: "${newURLData.id}" already in use.`
- }
- }
-
- let user: UserModel | null = null;
- let dynamicURLs: DynamicURLModel[] = [];
- try {
- user = await locals.pb.collection("users").getOne(locals.user.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: "No User."
- }
- }
-
- if (!user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- try {
- dynamicURLs = await locals.pb.collection('dynamic_url').getFullList({
- filter: `owner = "${user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: "Internal Server Error."
- }
- }
-
- if (dynamicURLs.length > 0 && user.accessLevel === "none") {
- return {
- error: true,
- msg: "Exceed the amount of dynamic URLs on the free plan."
- }
- }
-
- try {
- let data: Partial = {
- "id": newURLData.id,
- "defaultRedirectTo": newURLData.defaultRedirectTo,
- "timeZone": newURLData.timeZone,
- "weekSheet": [[],[],[],[],[],[],[]],
- "refs": [],
- "owner": locals.user.id
- };
-
- await locals.pb.collection('dynamic_url').create(data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: "Internal Server Error."
- }
- }
-
- getMyDynamicURLS().refresh();
- return {
- error: false,
- msg: "Successfully created dynamic URL."
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ let dynamicURL: DynamicURLModel | null = null;
+ try {
+ dynamicURL = await locals.pb.collection("dynamic_url").getOne(newURLData.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (_err) {
+ //All Good
+ }
+
+ if (dynamicURL) {
+ return {
+ error: true,
+ msg: `URL ID: "${newURLData.id}" already in use.`
+ };
+ }
+
+ let user: UserModel | null = null;
+ let dynamicURLs: DynamicURLModel[] = [];
+ try {
+ user = await locals.pb.collection("users").getOne(locals.user.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ if (!user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ try {
+ dynamicURLs = await locals.pb.collection("dynamic_url").getFullList({
+ filter: `owner = "${user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: "Internal Server Error."
+ };
+ }
+
+ if (dynamicURLs.length > 0 && user.accessLevel === "none") {
+ return {
+ error: true,
+ msg: "Exceed the amount of dynamic URLs on the free plan."
+ };
+ }
+
+ try {
+ let data: Partial = {
+ id: newURLData.id,
+ defaultRedirectTo: newURLData.defaultRedirectTo,
+ timeZone: newURLData.timeZone,
+ weekSheet: [[], [], [], [], [], [], []],
+ refs: [],
+ owner: locals.user.id,
+ overrideExpireInStr: "never"
+ };
+
+ await locals.pb.collection("dynamic_url").create(data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: "Internal Server Error."
+ };
+ }
+
+ getMyDynamicURLS().refresh();
+ return {
+ error: false,
+ msg: "Successfully created dynamic URL."
+ };
});
const UpdateDynamicURLSchema = v.object({
- id: v.string(),
- defaultRedirectTo: v.pipe(v.string(), v.url()),
- timeZone: v.string(),
- weekSheet: v.array(v.array(v.object({
- startMinute: v.number(),
- endMinute: v.number(),
- link: v.pipe(v.string(), v.url())
- }))),
- enableWeekSheet: v.boolean(),
- enableOverrideRedirect: v.boolean(),
- disableURL: v.boolean(),
- overrideRedirectTo: v.nullish(v.union([v.pipe(v.string(), v.url()), v.pipe(v.string(), v.maxLength(0))])),
+ id: v.string(),
+ defaultRedirectTo: v.pipe(v.string(), v.url()),
+ timeZone: v.string(),
+ weekSheet: v.array(
+ v.array(
+ v.object({
+ startMinute: v.number(),
+ endMinute: v.number(),
+ link: v.pipe(v.string(), v.url())
+ })
+ )
+ ),
+ enableWeekSheet: v.boolean(),
+ enableOverrideRedirect: v.boolean(),
+ overrideExpiresIn: v.string(),
+ disableURL: v.boolean(),
+ overrideRedirectTo: v.nullish(
+ v.union([v.pipe(v.string(), v.url()), v.pipe(v.string(), v.maxLength(0))])
+ )
});
export const updateDynamicURLCommand = command(UpdateDynamicURLSchema, async (updatedURLData) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- if (updatedURLData.enableOverrideRedirect && !updatedURLData.overrideRedirectTo) {
- return {
- error: true,
- msg: "No Override URL provided."
- }
- }
-
- let dynamicURL: DynamicURLModel | null = null;
- try {
- dynamicURL = await locals.pb.collection("dynamic_url").getOne(updatedURLData.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch(_err) {
- return {
- error: true,
- msg: `Dynamic URL not found.`
- }
- }
-
- if (dynamicURL === null) {
- return {
- error: true,
- msg: `Dynamic URL not found.`
- }
- }
-
- if (dynamicURL.owner !== locals.user.id) {
- return {
- error: true,
- msg: `Unauthorized.`
- }
- }
-
- try {
- let data: Partial = {
- "defaultRedirectTo": updatedURLData.defaultRedirectTo,
- "timeZone": updatedURLData.timeZone,
- "weekSheet": updatedURLData.weekSheet,
- "enableWeekSheet": updatedURLData.enableWeekSheet,
- "enableOverrideRedirect": updatedURLData.enableOverrideRedirect,
- "disableURL": updatedURLData.disableURL,
- };
-
- if (updatedURLData.enableOverrideRedirect && updatedURLData.overrideRedirectTo && updatedURLData.overrideRedirectTo.length > 0) {
- data["overrideRedirectTo"] = updatedURLData.overrideRedirectTo;
- }
-
- await locals.pb.collection('dynamic_url').update(dynamicURL.id, data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: `Internal Server Error.`
- }
- }
-
- getMyDynamicURLS().refresh();
- return {
- error: false,
- msg: "Successfully updated dynamic URL."
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ if (updatedURLData.enableOverrideRedirect && !updatedURLData.overrideRedirectTo) {
+ return {
+ error: true,
+ msg: "No Override URL provided."
+ };
+ }
+
+ let dynamicURL: DynamicURLModel | null = null;
+ try {
+ dynamicURL = await locals.pb.collection("dynamic_url").getOne(updatedURLData.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (_err) {
+ return {
+ error: true,
+ msg: `Dynamic URL not found.`
+ };
+ }
+
+ if (dynamicURL === null) {
+ return {
+ error: true,
+ msg: `Dynamic URL not found.`
+ };
+ }
+
+ if (dynamicURL.owner !== locals.user.id) {
+ return {
+ error: true,
+ msg: `Unauthorized.`
+ };
+ }
+
+ try {
+ let data: Partial = {
+ defaultRedirectTo: updatedURLData.defaultRedirectTo,
+ timeZone: updatedURLData.timeZone,
+ weekSheet: updatedURLData.weekSheet,
+ enableWeekSheet: updatedURLData.enableWeekSheet,
+ enableOverrideRedirect: updatedURLData.enableOverrideRedirect,
+ disableURL: updatedURLData.disableURL
+ };
+
+ if (
+ updatedURLData.enableOverrideRedirect &&
+ updatedURLData.overrideRedirectTo &&
+ updatedURLData.overrideRedirectTo.length > 0
+ ) {
+ data["overrideRedirectTo"] = updatedURLData.overrideRedirectTo;
+ }
+
+ if (updatedURLData.enableOverrideRedirect) {
+ data["overrideExpiresIn"] = updatedURLData.overrideExpiresIn;
+ data["overrideExpireInStr"] = "set";
+ }
+
+ await locals.pb.collection("dynamic_url").update(dynamicURL.id, data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: `Internal Server Error.`
+ };
+ }
+
+ getMyDynamicURLS().refresh();
+ return {
+ error: false,
+ msg: "Successfully updated dynamic URL."
+ };
});
const DeleteDynamicURLSchema = v.object({
- id: v.string(),
+ id: v.string()
});
export const deleteDynamicURLCommand = command(DeleteDynamicURLSchema, async (updatedURLData) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- let dynamicURL: DynamicURLModel | null = null;
- try {
- dynamicURL = await locals.pb.collection("dynamic_url").getOne(updatedURLData.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch(_err) {
- return {
- error: true,
- msg: `Dynamic URL not found.`
- }
- }
-
- if (dynamicURL === null) {
- return {
- error: true,
- msg: `Dynamic URL not found.`
- }
- }
-
- if (dynamicURL.owner !== locals.user.id) {
- return {
- error: true,
- msg: `Unauthorized.`
- }
- }
-
- try {
- await locals.pb.collection('dynamic_url').delete(dynamicURL.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: `Internal Server Error.`
- }
- }
-
- getMyDynamicURLS().refresh();
- return {
- error: false,
- msg: "Successfully deleted dynamic URL."
- }
-});
\ No newline at end of file
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ let dynamicURL: DynamicURLModel | null = null;
+ try {
+ dynamicURL = await locals.pb.collection("dynamic_url").getOne(updatedURLData.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (_err) {
+ return {
+ error: true,
+ msg: `Dynamic URL not found.`
+ };
+ }
+
+ if (dynamicURL === null) {
+ return {
+ error: true,
+ msg: `Dynamic URL not found.`
+ };
+ }
+
+ if (dynamicURL.owner !== locals.user.id) {
+ return {
+ error: true,
+ msg: `Unauthorized.`
+ };
+ }
+
+ try {
+ await locals.pb.collection("dynamic_url").delete(dynamicURL.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: `Internal Server Error.`
+ };
+ }
+
+ getMyDynamicURLS().refresh();
+ return {
+ error: false,
+ msg: "Successfully deleted dynamic URL."
+ };
+});
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/+page.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/+page.server.ts
index 6ebd1d1..10eedb8 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/+page.server.ts
@@ -1,3 +1,3 @@
export async function load({ parent }) {
- await parent();
-}
\ No newline at end of file
+ await parent();
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/+page.svelte b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/+page.svelte
index 40e7055..b52c2ae 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/+page.svelte
@@ -1,218 +1,225 @@
- My Event Lists | InfoSections
+ My Event Lists | InfoSections
-
-
-
My Event Lists
-
Any upcoming events can be displayed as an event list in any project that supports iframes.
-
-
-
-
-
- New Event List
-
-
-
- Create New Event List
- You can change more settings after creating the list.
-
-
-
-
- List Name
-
-
-
-
- Description
-
-
-
-
-
- newEListDialogOpen = false}>
- Cancel
-
-
- {#if creatingEList}
-
- Creating Event List...
- {:else}
- Create Event List
- {/if}
-
-
-
-
-
-
-
- {#if (await getMyEventLists()).length === 0}
-
No Event Lists Yet.
- {/if}
- {#each await getMyEventLists() as eventList (`eventFeedlist${eventList.id}`)}
-
-
-
-
-
-
-
-
-
-
- {eventList.name}
-
- /elist/{eventList.id}
-
-
-
-
-
-
-
-
- copyEListLinkToClipboard(eventList.id)}
- >
-
- Copy Link
-
- {deleteEListId = eventList.id}}>
-
- Delete
-
-
-
-
-
-
-
- {eventList.description}
-
-
-
-
- {eventList.visits} Visit{eventList.visits === 1 ? "" : "s"}
-
-
-
- View Details
-
-
-
-
- {/each}
-
+
+
+
My Event Lists
+
+ Any upcoming events can be displayed as an event list in any project that supports iframes.
+
+
+
+
+
+
+ New Event List
+
+
+
+ Create New Event List
+ You can change more settings after creating the list.
+
+
+
+
+ List Name
+
+
+
+
+ Description
+
+
+
+
+
+ (newEListDialogOpen = false)}>Cancel
+
+ {#if creatingEList}
+
+ Creating Event List...
+ {:else}
+ Create Event List
+ {/if}
+
+
+
+
+
+
+
+ {#if (await getMyEventLists()).length === 0}
+
+ No Event Lists Yet.
+
+ {/if}
+ {#each await getMyEventLists() as eventList (`eventFeedlist${eventList.id}`)}
+
+
+
+
+
+
+
+
+
+
+ {eventList.name}
+
+ /elist/{eventList.id}
+
+
+
+
+
+
+
+
+ copyEListLinkToClipboard(eventList.id)}>
+
+ Copy Link
+
+ {
+ deleteEListId = eventList.id;
+ }}
+ >
+
+ Delete
+
+
+
+
+
+
+
+ {eventList.description}
+
+
+
+
+ {eventList.visits} Visit{eventList.visits === 1 ? "" : "s"}
+
+
+
+ View Details
+
+
+
+
+ {/each}
+
- deleteEListId = null}>
-
-
- Are you sure absolutely sure?
-
- This action cannot be undone. This will permanently delete this event list from our servers. All links relying on this list will not work.
-
-
- Confirm Delete
-
-
-
-
\ No newline at end of file
+ (deleteEListId = null)}>
+
+
+ Are you sure absolutely sure?
+
+ This action cannot be undone. This will permanently delete this event list from our servers.
+ All links relying on this list will not work.
+
+
+ Confirm Delete
+
+
+
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/[slug]/+page.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/[slug]/+page.server.ts
index 8c6d64f..85ecad6 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/[slug]/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/[slug]/+page.server.ts
@@ -3,11 +3,11 @@ import { config } from "dotenv";
config();
export async function load({ params, parent }) {
- await parent();
- let slug = params.slug;
+ await parent();
+ let slug = params.slug;
- return {
- selectedlistId: slug,
- apiServer: process.env["PB_URL"]!
- }
-}
\ No newline at end of file
+ return {
+ selectedlistId: slug,
+ apiServer: process.env["PB_URL"]!
+ };
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/[slug]/+page.svelte b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/[slug]/+page.svelte
index 5c1b3e1..5d721f3 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/[slug]/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/[slug]/+page.svelte
@@ -1,154 +1,167 @@
- {selectedEventList.name} | InfoSections
+ {selectedEventList.name} | InfoSections
-
-
-
-
-
-
{selectedEventList.name} | List Details
-
Manage your event list settings and filters.
-
-
-
-
-
-
{
- let savingChanges = toast.loading("Saving Changes.", { duration: Number.POSITIVE_INFINITY });
- try {
- await submit();
- form.reset();
- clearFileInput(document.getElementById("imageUploaderEventList"));
- uploadNewAvatar = null;
- toast.dismiss(savingChanges);
-
- toast.success("Saved Changes");
- } catch (err) {
- console.log(err);
- toast.dismiss(savingChanges);
- toast.error("An error occured.");
- }
- })}
- class="space-y-6"
- enctype="multipart/form-data"
- >
- Update Image Feed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
{selectedEventList.name} | List Details
+
Manage your event list settings and filters.
+
+
+
+
+
+
{
+ let savingChanges = toast.loading("Saving Changes.", {
+ duration: Number.POSITIVE_INFINITY
+ });
+ try {
+ await submit();
+ form.reset();
+ clearFileInput(document.getElementById("imageUploaderEventList"));
+ uploadNewAvatar = null;
+ toast.dismiss(savingChanges);
+
+ toast.success("Saved Changes");
+ } catch (err) {
+ console.log(err);
+ toast.dismiss(savingChanges);
+ toast.error("An error occured.");
+ }
+ })}
+ class="space-y-6"
+ enctype="multipart/form-data"
+ >
+ Update Image Feed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
+ .stickySidebar {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ }
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/eventListActions.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/eventListActions.remote.ts
index efe132a..69d8d58 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/event-lists/eventListActions.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/event-lists/eventListActions.remote.ts
@@ -1,4 +1,8 @@
-import { defaultEventListFilters, defaultImageListCustomizations, type EventListDBModel } from "@/utils";
+import {
+ defaultEventListFilters,
+ defaultImageListCustomizations,
+ type EventListDBModel
+} from "@/utils";
import { getEventListById, getMyEventLists } from "../backend.remote";
import { command, form, getRequestEvent } from "$app/server";
import { invalid, redirect } from "@sveltejs/kit";
@@ -8,160 +12,167 @@ import * as v from "valibot";
config();
const CreateEventListSchema = v.object({
- name: v.pipe(v.string(), v.nonEmpty("A name for the list is required.")),
- description: v.pipe(v.string(), v.nonEmpty("A description for the list is required."))
+ name: v.pipe(v.string(), v.nonEmpty("A name for the list is required.")),
+ description: v.pipe(v.string(), v.nonEmpty("A description for the list is required."))
});
export const createEventListCommand = command(CreateEventListSchema, async (newEList) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const eventLists = await getMyEventLists();
-
- if (eventLists.length > 1 && locals.user.accessLevel === "none") {
- return {
- error: true,
- msg: "Exceed the amount of event lists on the free plan."
- }
- }
-
- try {
- let data: Partial = {
- "name": newEList.name,
- "description": newEList.description,
- "owner": locals.user.id,
- "displaySettings": defaultImageListCustomizations,
- "filters": defaultEventListFilters
- };
-
- await locals.pb.collection('eventLists').create(data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: "Internal Server Error."
- }
- }
-
- getMyEventLists().refresh();
- return {
- error: false,
- msg: "Successfully created event list."
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const eventLists = await getMyEventLists();
+
+ if (eventLists.length > 1 && locals.user.accessLevel === "none") {
+ return {
+ error: true,
+ msg: "Exceed the amount of event lists on the free plan."
+ };
+ }
+
+ try {
+ let data: Partial = {
+ name: newEList.name,
+ description: newEList.description,
+ owner: locals.user.id,
+ displaySettings: defaultImageListCustomizations,
+ filters: defaultEventListFilters
+ };
+
+ await locals.pb.collection("eventLists").create(data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: "Internal Server Error."
+ };
+ }
+
+ getMyEventLists().refresh();
+ return {
+ error: false,
+ msg: "Successfully created event list."
+ };
});
const UpdateEventListSchema = v.object({
- id: v.string(),
- name: v.string(),
- description: v.string(),
- avatarLink: v.optional(v.string()),
- newAvatar: v.optional(v.any()),
- displaySettings: v.object({
- displayStyle: v.picklist(["minimal", "expanded"]),
- showEventName: v.optional(v.boolean(), false),
- showEventDescription: v.optional(v.boolean(), false),
- showEventRegistration: v.optional(v.boolean(), false),
- showUpcomingEventsTextAndDesc: v.optional(v.boolean(), false),
- setTransparentBackground: v.optional(v.boolean(), false)
- }),
- filters: v.optional(v.object({
- onlyShowFeatured: v.optional(v.boolean(), false),
- hideUnpublished: v.optional(v.boolean(), false),
- hideRecurringEvents: v.optional(v.boolean(), false)
- }), undefined)
+ id: v.string(),
+ name: v.string(),
+ description: v.string(),
+ avatarLink: v.optional(v.string()),
+ newAvatar: v.optional(v.any()),
+ displaySettings: v.object({
+ displayStyle: v.picklist(["minimal", "expanded"]),
+ showEventName: v.optional(v.boolean(), false),
+ showEventDescription: v.optional(v.boolean(), false),
+ showEventRegistration: v.optional(v.boolean(), false),
+ showUpcomingEventsTextAndDesc: v.optional(v.boolean(), false),
+ setTransparentBackground: v.optional(v.boolean(), false)
+ }),
+ filters: v.optional(
+ v.object({
+ onlyShowFeatured: v.optional(v.boolean(), false),
+ hideUnpublished: v.optional(v.boolean(), false),
+ hideRecurringEvents: v.optional(v.boolean(), false)
+ }),
+ undefined
+ )
});
export const updateEventListForm = form(UpdateEventListSchema, async (updatedEList, issue) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return redirect(303, "/");
- }
-
- const currentEventList = await getEventListById(updatedEList.id);
-
- try {
- let data: Partial = {
- "name": updatedEList.name,
- "description": updatedEList.description,
- "displaySettings": updatedEList.displaySettings,
- "filters": updatedEList.filters ? updatedEList.filters : {
- "hideUnpublished": false,
- "onlyShowFeatured": false,
- "hideRecurringEvents": false
- }
- };
-
- if (!updatedEList.avatarLink && updatedEList.newAvatar) {
- const newAvatar = updatedEList.newAvatar as File;
- const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, { type: newAvatar.type });
- data["logo"] = file;
- }
-
- if (!updatedEList.avatarLink && !updatedEList.newAvatar) {
- data["logo"] = "";
- }
-
- await locals.pb.collection('eventLists').update(currentEventList.id, data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return invalid(issue.id("Failed to update event list."));
- }
-
- getMyEventLists().refresh();
- getEventListById(currentEventList.id).refresh();
- return {
- error: false,
- msg: "Successfully updated event list."
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return redirect(303, "/");
+ }
+
+ const currentEventList = await getEventListById(updatedEList.id);
+
+ try {
+ let data: Partial = {
+ name: updatedEList.name,
+ description: updatedEList.description,
+ displaySettings: updatedEList.displaySettings,
+ filters: updatedEList.filters
+ ? updatedEList.filters
+ : {
+ hideUnpublished: false,
+ onlyShowFeatured: false,
+ hideRecurringEvents: false
+ }
+ };
+
+ if (!updatedEList.avatarLink && updatedEList.newAvatar) {
+ const newAvatar = updatedEList.newAvatar as File;
+ const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, {
+ type: newAvatar.type
+ });
+ data["logo"] = file;
+ }
+
+ if (!updatedEList.avatarLink && !updatedEList.newAvatar) {
+ data["logo"] = "";
+ }
+
+ await locals.pb.collection("eventLists").update(currentEventList.id, data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return invalid(issue.id("Failed to update event list."));
+ }
+
+ getMyEventLists().refresh();
+ getEventListById(currentEventList.id).refresh();
+ return {
+ error: false,
+ msg: "Successfully updated event list."
+ };
});
export const deleteEventListCommand = command(v.string(), async (id) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const eventList = await getEventListById(id);
-
- try {
- await locals.pb.collection('eventLists').delete(eventList.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: `Internal Server Error.`
- }
- }
-
- getMyEventLists().refresh();
- return {
- error: false,
- msg: "Successfully deleted event list."
- }
-});
\ No newline at end of file
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const eventList = await getEventListById(id);
+
+ try {
+ await locals.pb.collection("eventLists").delete(eventList.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: `Internal Server Error.`
+ };
+ }
+
+ getMyEventLists().refresh();
+ return {
+ error: false,
+ msg: "Successfully deleted event list."
+ };
+});
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/events.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/events.remote.ts
index 9fc924c..6abb024 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/events.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/events.remote.ts
@@ -1,100 +1,107 @@
import { command, getRequestEvent, query } from "$app/server";
-import type { EventDBModelPrivateExpanded, EventResourcesDBModelPrivate, EventTagsDBModelPrivate } from "@/event.utils";
+import type {
+ EventDBModelPrivateExpanded,
+ EventResourcesDBModelPrivate,
+ EventTagsDBModelPrivate
+} from "@/event.utils";
import { getMyIntegrations } from "./backend.remote";
import { config } from "dotenv";
config();
export const getAllUserEvents = query(async () => {
- const { locals } = getRequestEvent();
- let events: EventDBModelPrivateExpanded[] = [];
-
- if (!locals.user) return events;
-
- try {
- events = await locals.pb.collection('events').getFullList({
- filter: `owner="${locals.user.id}"`,
- expand: "tags,resources",
- sort: 'startTime',
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to events.", err);
- }
-
- return events;
+ const { locals } = getRequestEvent();
+ let events: EventDBModelPrivateExpanded[] = [];
+
+ if (!locals.user) return events;
+
+ try {
+ events = await locals.pb.collection("events").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ expand: "tags,resources",
+ sort: "startTime",
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to events.", err);
+ }
+
+ return events;
});
export const updateSpecificUserEvents = command(async () => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const response = await fetch(process.env["PB_URL"] + `updateSpecificUserEvents/${locals.user.id}`, {
- method: 'PATCH',
- headers: {
- "X-PCO-Webhooks-Authenticity": locals.user.customerId
- }
- });
- if (!response.ok) {
- console.log(response);
- return {
- error: false,
- msg: "Failed to refetch events."
- }
- }
-
- getMyIntegrations().refresh();
- getAllUserEvents().refresh();
- return {
- error: false,
- msg: "Successful sync"
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const response = await fetch(
+ process.env["PB_URL"] + `updateSpecificUserEvents/${locals.user.id}`,
+ {
+ method: "PATCH",
+ headers: {
+ "X-PCO-Webhooks-Authenticity": locals.user.customerId
+ }
+ }
+ );
+ if (!response.ok) {
+ console.log(response);
+ return {
+ error: false,
+ msg: "Failed to refetch events."
+ };
+ }
+
+ getMyIntegrations().refresh();
+ getAllUserEvents().refresh();
+ return {
+ error: false,
+ msg: "Successful sync"
+ };
});
export const getMyEventTagsPrivate = query(async () => {
- const { locals } = getRequestEvent();
- let tags: EventTagsDBModelPrivate[] = [];
-
- if (!locals.user) return tags;
-
- try {
- tags = await locals.pb.collection('tags').getFullList({
- filter: `owner="${locals.user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to fetch tags.", err);
- }
-
- return tags;
+ const { locals } = getRequestEvent();
+ let tags: EventTagsDBModelPrivate[] = [];
+
+ if (!locals.user) return tags;
+
+ try {
+ tags = await locals.pb.collection("tags").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to fetch tags.", err);
+ }
+
+ return tags;
});
export const getMyEventResourcesPrivate = query(async () => {
- const { locals } = getRequestEvent();
- let resources: EventResourcesDBModelPrivate[] = [];
-
- if (!locals.user) return resources;
-
- try {
- resources = await locals.pb.collection('resources').getFullList({
- filter: `owner="${locals.user.id}"`,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to fetch resources.", err);
- }
-
- return resources;
-});
\ No newline at end of file
+ const { locals } = getRequestEvent();
+ let resources: EventResourcesDBModelPrivate[] = [];
+
+ if (!locals.user) return resources;
+
+ try {
+ resources = await locals.pb.collection("resources").getFullList({
+ filter: `owner="${locals.user.id}"`,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Failed to fetch resources.", err);
+ }
+
+ return resources;
+});
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/+page.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/+page.server.ts
index 6ebd1d1..10eedb8 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/+page.server.ts
@@ -1,3 +1,3 @@
export async function load({ parent }) {
- await parent();
-}
\ No newline at end of file
+ await parent();
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/+page.svelte b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/+page.svelte
index d4f6465..d042f2f 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/+page.svelte
@@ -1,217 +1,225 @@
- My Image Feeds | InfoSections
+ My Image Feeds | InfoSections
-
-
-
My Image Feeds
-
Any upcoming events containing an image, can be displayed as an image feed in any project that supports iframes.
-
-
-
-
-
- New Image Feed
-
-
-
- Create New Image Feed
- You can change more settings after creating the feed.
-
-
-
-
- Feed Name
-
-
-
-
- Description
-
-
-
-
-
- newIFeedDialogOpen = false}>
- Cancel
-
-
- {#if creatingIFeed}
-
- Creating Image Feed...
- {:else}
- Create Image Feed
- {/if}
-
-
-
-
-
-
-
- {#if (await getMyImageFeeds()).length === 0}
-
No Image Feeds Yet.
- {/if}
- {#each await getMyImageFeeds() as imageFeed (`imageFeedlist${imageFeed.id}`)}
-
-
-
-
-
-
-
-
-
-
- {imageFeed.name}
-
- /ifeed/{imageFeed.id}
-
-
-
-
-
-
-
-
- copyIFeedLinkToClipboard(imageFeed.id)}
- >
-
- Copy Link
-
- {deleteIFeedId = imageFeed.id}}>
-
- Delete
-
-
-
-
-
-
-
- {imageFeed.description}
-
-
-
-
- {imageFeed.visits} Visit{imageFeed.visits === 1 ? "" : "s"}
-
-
-
- View Details
-
-
-
-
- {/each}
-
+
+
+
My Image Feeds
+
+ Any upcoming events containing an image, can be displayed as an image feed in any project
+ that supports iframes.
+
+
+
+
+
+
+ New Image Feed
+
+
+
+ Create New Image Feed
+ You can change more settings after creating the feed.
+
+
+
+
+ Feed Name
+
+
+
+
+ Description
+
+
+
+
+
+ (newIFeedDialogOpen = false)}>Cancel
+
+ {#if creatingIFeed}
+
+ Creating Image Feed...
+ {:else}
+ Create Image Feed
+ {/if}
+
+
+
+
+
+
+
+ {#if (await getMyImageFeeds()).length === 0}
+
+ No Image Feeds Yet.
+
+ {/if}
+ {#each await getMyImageFeeds() as imageFeed (`imageFeedlist${imageFeed.id}`)}
+
+
+
+
+
+
+
+
+
+
+ {imageFeed.name}
+
+ /ifeed/{imageFeed.id}
+
+
+
+
+
+
+
+
+ copyIFeedLinkToClipboard(imageFeed.id)}>
+
+ Copy Link
+
+ {
+ deleteIFeedId = imageFeed.id;
+ }}
+ >
+
+ Delete
+
+
+
+
+
+
+
+ {imageFeed.description}
+
+
+
+
+ {imageFeed.visits} Visit{imageFeed.visits === 1 ? "" : "s"}
+
+
+
+ View Details
+
+
+
+
+ {/each}
+
- deleteIFeedId = null}>
-
-
- Are you sure absolutely sure?
-
- This action cannot be undone. This will permanently delete this image feed from our servers. All links relying on this feed will not work.
-
-
- Confirm Delete
-
-
-
-
\ No newline at end of file
+ (deleteIFeedId = null)}>
+
+
+ Are you sure absolutely sure?
+
+ This action cannot be undone. This will permanently delete this image feed from our servers.
+ All links relying on this feed will not work.
+
+
+ Confirm Delete
+
+
+
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/IFeedCustomImageActions.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/IFeedCustomImageActions.remote.ts
index 2023032..3918e1b 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/IFeedCustomImageActions.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/IFeedCustomImageActions.remote.ts
@@ -8,233 +8,254 @@ import * as v from "valibot";
config();
const CreateCustomImageSchema = v.object({
- picture: v.any(),
- showLink: v.pipe(v.optional(v.boolean(), false), v.transform((val) => val ?? false)),
- linkText: v.pipe(v.optional(v.string(), ""), v.transform((val) => val ?? "")),
- registrationURL: v.optional(
- v.union([
- v.pipe(v.string(), v.url()),
- v.literal("")
- ]),
- ""
- ),
- currentIFeed: v.string()
+ picture: v.any(),
+ showLink: v.pipe(
+ v.optional(v.boolean(), false),
+ v.transform((val) => val ?? false)
+ ),
+ linkText: v.pipe(
+ v.optional(v.string(), ""),
+ v.transform((val) => val ?? "")
+ ),
+ registrationURL: v.optional(v.union([v.pipe(v.string(), v.url()), v.literal("")]), ""),
+ currentIFeed: v.string()
});
-export const createCustomImageForm = form(CreateCustomImageSchema, async (newCustomImage, issue) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return redirect(303, "/");
- }
-
- if (locals.user.accessLevel === "none") {
- return invalid(issue.registrationURL("You need to be subsrcibed to use this feature."));
- }
-
- if (newCustomImage.showLink && !newCustomImage.registrationURL) {
- return invalid(issue.registrationURL("Link URL cannot be empty."))
- }
-
- if (newCustomImage.showLink && newCustomImage.linkText.length === 0) {
- return invalid(issue.linkText("Link Button Text cannot be empty."))
- }
-
- try {
- let data: Partial = {
- "linkText": newCustomImage.linkText,
- "registrationURL": newCustomImage.registrationURL,
- "showLink": newCustomImage.showLink,
- "imageFeed": [ newCustomImage.currentIFeed ],
- "owner": locals.user.id
- };
-
- if (newCustomImage.picture) {
- const newAvatar = newCustomImage.picture as File;
- const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, { type: newAvatar.type });
- data["picture"] = file;
- } else {
- return invalid(issue.registrationURL("A Picture is required."));
- }
-
- await locals.pb.collection('customImageIfeed').create(data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return invalid(issue.registrationURL("Failed to create custom image."));
- }
-
- getCustomImagesForIFeed(newCustomImage.currentIFeed).refresh();
- return {
- error: false,
- msg: "Successfully created image feed."
- }
-});
-
-export const getCustomImageById = command(v.string(), async (id): Promise<({
- error: true,
- msg: string
-} | {
- error: false,
- customImageIfeed: CustomImageIFeedDBModel
-})> => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- let customImageIfeed: CustomImageIFeedDBModel | null = null;
- try {
- customImageIfeed = await locals.pb.collection("customImageIfeed").getOne(id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch(err) {
- console.log(err);
- return {
- error: true,
- msg: "Internal Server Error."
- }
- }
-
- if (customImageIfeed === null) {
- return {
- error: true,
- msg: "Custom Image doesnt exist."
- }
- }
-
- if (customImageIfeed.owner !== locals.user.id) {
- return {
- error: true,
- msg: "Unauthorized."
- }
- }
-
- return {
- error: false,
- customImageIfeed
- }
-});
+export const createCustomImageForm = form(
+ CreateCustomImageSchema,
+ async (newCustomImage, issue) => {
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return redirect(303, "/");
+ }
+
+ if (locals.user.accessLevel === "none") {
+ return invalid(issue.registrationURL("You need to be subsrcibed to use this feature."));
+ }
+
+ if (newCustomImage.showLink && !newCustomImage.registrationURL) {
+ return invalid(issue.registrationURL("Link URL cannot be empty."));
+ }
+
+ if (newCustomImage.showLink && newCustomImage.linkText.length === 0) {
+ return invalid(issue.linkText("Link Button Text cannot be empty."));
+ }
+
+ try {
+ let data: Partial = {
+ linkText: newCustomImage.linkText,
+ registrationURL: newCustomImage.registrationURL,
+ showLink: newCustomImage.showLink,
+ imageFeed: [newCustomImage.currentIFeed],
+ owner: locals.user.id
+ };
+
+ if (newCustomImage.picture) {
+ const newAvatar = newCustomImage.picture as File;
+ const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, {
+ type: newAvatar.type
+ });
+ data["picture"] = file;
+ } else {
+ return invalid(issue.registrationURL("A Picture is required."));
+ }
+
+ await locals.pb.collection("customImageIfeed").create(data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return invalid(issue.registrationURL("Failed to create custom image."));
+ }
+
+ getCustomImagesForIFeed(newCustomImage.currentIFeed).refresh();
+ return {
+ error: false,
+ msg: "Successfully created image feed."
+ };
+ }
+);
+
+export const getCustomImageById = command(
+ v.string(),
+ async (
+ id
+ ): Promise<
+ | {
+ error: true;
+ msg: string;
+ }
+ | {
+ error: false;
+ customImageIfeed: CustomImageIFeedDBModel;
+ }
+ > => {
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ let customImageIfeed: CustomImageIFeedDBModel | null = null;
+ try {
+ customImageIfeed = await locals.pb.collection("customImageIfeed").getOne(id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+ return {
+ error: true,
+ msg: "Internal Server Error."
+ };
+ }
+
+ if (customImageIfeed === null) {
+ return {
+ error: true,
+ msg: "Custom Image doesnt exist."
+ };
+ }
+
+ if (customImageIfeed.owner !== locals.user.id) {
+ return {
+ error: true,
+ msg: "Unauthorized."
+ };
+ }
+
+ return {
+ error: false,
+ customImageIfeed
+ };
+ }
+);
const UpdateCustomImageSchema = v.object({
- id: v.string(),
- eventPictureLink: v.optional(v.string()),
- included: v.array(v.string()),
- uploadNewEventPicture: v.optional(v.any()),
- showLink: v.optional(v.boolean(), false),
- linkText: v.optional(v.string(), ""),
- registrationURL: v.optional(v.pipe(v.string(), v.url("Invalid Link URL. Please enter a valid URL."))),
- currentIFeedId: v.string()
-});
-
-export const updateCustomImageForm = form(UpdateCustomImageSchema, async (updatedCustomImage, issue) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return redirect(303, "/");
- }
-
- if (locals.user.accessLevel === "none") {
- return invalid(issue.registrationURL("You need to be subscibed to use this feature."));
- }
-
- if (updatedCustomImage.showLink && !updatedCustomImage.registrationURL) {
- return invalid(issue.registrationURL("Link URL cannot be empty."))
- }
-
- if (updatedCustomImage.showLink && updatedCustomImage.linkText.length === 0) {
- return invalid(issue.linkText("Link Button Text cannot be empty."))
- }
-
- if (!updatedCustomImage.eventPictureLink && !updatedCustomImage.uploadNewEventPicture) {
- return invalid(issue.registrationURL("Image is missing."));
-
- }
-
- const response = await getCustomImageById(updatedCustomImage.id);
-
- if (response.error) return redirect(303, "/dashboard/image-feeds");
-
- const { customImageIfeed } = response;
-
- try {
- let data: Partial = {
- "linkText": updatedCustomImage.linkText,
- "registrationURL": updatedCustomImage.registrationURL,
- "showLink": updatedCustomImage.showLink,
- "imageFeed": updatedCustomImage.included
- };
-
- if (updatedCustomImage.uploadNewEventPicture) {
- const newAvatar = updatedCustomImage.uploadNewEventPicture as File;
- const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, { type: newAvatar.type });
- data["picture"] = file;
- }
-
-
- await locals.pb.collection('customImageIfeed').update(customImageIfeed.id, data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return invalid(issue.id("Failed to update custom image."));
- }
-
- getCustomImagesForIFeed(updatedCustomImage.currentIFeedId).refresh();
- return {
- error: false,
- msg: "Successfully updated custom image."
- }
+ id: v.string(),
+ eventPictureLink: v.optional(v.string()),
+ included: v.array(v.string()),
+ uploadNewEventPicture: v.optional(v.any()),
+ showLink: v.optional(v.boolean(), false),
+ linkText: v.optional(v.string(), ""),
+ registrationURL: v.optional(
+ v.pipe(v.string(), v.url("Invalid Link URL. Please enter a valid URL."))
+ ),
+ currentIFeedId: v.string()
});
-export const deleteCustomImageCommand = command(v.object({
- id: v.string(),
- currentFeedID: v.string()
-}), async (data) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const response = await getCustomImageById(data.id);
-
- if (response.error) return response;
-
- try {
- await locals.pb.collection('customImageIfeed').delete(data.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: `Internal Server Error.`
- }
- }
-
- getCustomImagesForIFeed(data.currentFeedID).refresh();
- return {
- error: false,
- msg: "Successfully deleted custom image."
- }
-});
\ No newline at end of file
+export const updateCustomImageForm = form(
+ UpdateCustomImageSchema,
+ async (updatedCustomImage, issue) => {
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return redirect(303, "/");
+ }
+
+ if (locals.user.accessLevel === "none") {
+ return invalid(issue.registrationURL("You need to be subscibed to use this feature."));
+ }
+
+ if (updatedCustomImage.showLink && !updatedCustomImage.registrationURL) {
+ return invalid(issue.registrationURL("Link URL cannot be empty."));
+ }
+
+ if (updatedCustomImage.showLink && updatedCustomImage.linkText.length === 0) {
+ return invalid(issue.linkText("Link Button Text cannot be empty."));
+ }
+
+ if (!updatedCustomImage.eventPictureLink && !updatedCustomImage.uploadNewEventPicture) {
+ return invalid(issue.registrationURL("Image is missing."));
+ }
+
+ const response = await getCustomImageById(updatedCustomImage.id);
+
+ if (response.error) return redirect(303, "/dashboard/image-feeds");
+
+ const { customImageIfeed } = response;
+
+ try {
+ let data: Partial = {
+ linkText: updatedCustomImage.linkText,
+ registrationURL: updatedCustomImage.registrationURL,
+ showLink: updatedCustomImage.showLink,
+ imageFeed: updatedCustomImage.included
+ };
+
+ if (updatedCustomImage.uploadNewEventPicture) {
+ const newAvatar = updatedCustomImage.uploadNewEventPicture as File;
+ const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, {
+ type: newAvatar.type
+ });
+ data["picture"] = file;
+ }
+
+ await locals.pb.collection("customImageIfeed").update(customImageIfeed.id, data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return invalid(issue.id("Failed to update custom image."));
+ }
+
+ getCustomImagesForIFeed(updatedCustomImage.currentIFeedId).refresh();
+ return {
+ error: false,
+ msg: "Successfully updated custom image."
+ };
+ }
+);
+
+export const deleteCustomImageCommand = command(
+ v.object({
+ id: v.string(),
+ currentFeedID: v.string()
+ }),
+ async (data) => {
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const response = await getCustomImageById(data.id);
+
+ if (response.error) return response;
+
+ try {
+ await locals.pb.collection("customImageIfeed").delete(data.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: `Internal Server Error.`
+ };
+ }
+
+ getCustomImagesForIFeed(data.currentFeedID).refresh();
+ return {
+ error: false,
+ msg: "Successfully deleted custom image."
+ };
+ }
+);
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/IFeedIncludeCalActions.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/IFeedIncludeCalActions.remote.ts
index b1b84cc..cac9996 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/IFeedIncludeCalActions.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/IFeedIncludeCalActions.remote.ts
@@ -5,92 +5,106 @@ import * as v from "valibot";
config();
-export const updateIncludedCalendarsToIFeed = command(v.object({
- calIds: v.array(v.string()),
- currentFeedID: v.string()
-}), async (data) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const imageFeed = await getImageFeedById(data.currentFeedID);
- const myCalendars = await getMyCalendars();
- let newCals = imageFeed.additionalCalendars;
-
- for (let i=0;i cal.id === currentCalId).length === 0) continue
-
- newCals.push(currentCalId);
- }
-
- try {
- await locals.pb.collection('imageFeeds').update(imageFeed.id, {
- "additionalCalendars": newCals,
- }, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: "Failed to update the included calendars."
- }
- }
-
- getImageFeedById(data.currentFeedID).refresh();
- return {
- error: false,
- msg: "Successfully updated included calendars."
- }
-});
-
-export const removeIncludedCalendarsToIFeed = command(v.object({
- calId: v.string(),
- currentFeedID: v.string()
-}), async (data) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const imageFeed = await getImageFeedById(data.currentFeedID);
- try {
- await locals.pb.collection('imageFeeds').update(imageFeed.id, {
- "additionalCalendars": imageFeed.additionalCalendars.filter((cal) => cal !== data.calId),
- }, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: "Failed to update the included calendars."
- }
- }
-
- getImageFeedById(data.currentFeedID).refresh();
- return {
- error: false,
- msg: "Successfully updated included calendars."
- }
-});
\ No newline at end of file
+export const updateIncludedCalendarsToIFeed = command(
+ v.object({
+ calIds: v.array(v.string()),
+ currentFeedID: v.string()
+ }),
+ async (data) => {
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const imageFeed = await getImageFeedById(data.currentFeedID);
+ const myCalendars = await getMyCalendars();
+ let newCals = imageFeed.additionalCalendars;
+
+ for (let i = 0; i < data.calIds.length; i++) {
+ const currentCalId = data.calIds[i];
+ if (!currentCalId) continue;
+
+ if (imageFeed.additionalCalendars.includes(currentCalId)) continue;
+
+ if (myCalendars.filter((cal) => cal.id === currentCalId).length === 0) continue;
+
+ newCals.push(currentCalId);
+ }
+
+ try {
+ await locals.pb.collection("imageFeeds").update(
+ imageFeed.id,
+ {
+ additionalCalendars: newCals
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: "Failed to update the included calendars."
+ };
+ }
+
+ getImageFeedById(data.currentFeedID).refresh();
+ return {
+ error: false,
+ msg: "Successfully updated included calendars."
+ };
+ }
+);
+
+export const removeIncludedCalendarsToIFeed = command(
+ v.object({
+ calId: v.string(),
+ currentFeedID: v.string()
+ }),
+ async (data) => {
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const imageFeed = await getImageFeedById(data.currentFeedID);
+ try {
+ await locals.pb.collection("imageFeeds").update(
+ imageFeed.id,
+ {
+ additionalCalendars: imageFeed.additionalCalendars.filter((cal) => cal !== data.calId)
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: "Failed to update the included calendars."
+ };
+ }
+
+ getImageFeedById(data.currentFeedID).refresh();
+ return {
+ error: false,
+ msg: "Successfully updated included calendars."
+ };
+ }
+);
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/[slug]/+page.server.ts b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/[slug]/+page.server.ts
index ace8daf..9dafcc2 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/[slug]/+page.server.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/[slug]/+page.server.ts
@@ -3,10 +3,10 @@ import { config } from "dotenv";
config();
export async function load({ params, parent }) {
- await parent();
- let slug = params.slug;
+ await parent();
+ let slug = params.slug;
- return {
- selectedFeedId: slug
- }
-}
\ No newline at end of file
+ return {
+ selectedFeedId: slug
+ };
+}
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/[slug]/+page.svelte b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/[slug]/+page.svelte
index 909855c..08d7765 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/[slug]/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/[slug]/+page.svelte
@@ -1,162 +1,179 @@
- {selectedImageFeed.name} | InfoSections
+ {selectedImageFeed.name} | InfoSections
-
-
-
-
-
-
{selectedImageFeed.name} | Feed Details
-
Manage your image feed settings and filters.
-
-
-
-
-
-
{
- let savingChanges = toast.loading("Saving Changes.", { duration: Number.POSITIVE_INFINITY });
- try {
- await submit();
- form.reset();
- clearFileInput(document.getElementById("imageUploaderImageFeed"));
- uploadNewAvatar = null;
- toast.dismiss(savingChanges);
-
- toast.success("Saved Changes");
- } catch (err) {
- console.log(err);
- toast.dismiss(savingChanges);
- toast.error("An error occured.");
- }
- })}
- class="space-y-6"
- enctype="multipart/form-data"
- >
- Update Image Feed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
{selectedImageFeed.name} | Feed Details
+
Manage your image feed settings and filters.
+
+
+
+
+
+
{
+ let savingChanges = toast.loading("Saving Changes.", {
+ duration: Number.POSITIVE_INFINITY
+ });
+ try {
+ await submit();
+ form.reset();
+ clearFileInput(document.getElementById("imageUploaderImageFeed"));
+ uploadNewAvatar = null;
+ toast.dismiss(savingChanges);
+
+ toast.success("Saved Changes");
+ } catch (err) {
+ console.log(err);
+ toast.dismiss(savingChanges);
+ toast.error("An error occured.");
+ }
+ })}
+ class="space-y-6"
+ enctype="multipart/form-data"
+ >
+ Update Image Feed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
+ .stickySidebar {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ }
+
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/imageFeedActions.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/imageFeedActions.remote.ts
index ab9804a..d1faf5a 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/imageFeedActions.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/image-feeds/imageFeedActions.remote.ts
@@ -1,4 +1,8 @@
-import { defaultImageFeedCustomizations, defaultImageFeedFilters, type ImageFeedDBModel } from "@/utils";
+import {
+ defaultImageFeedCustomizations,
+ defaultImageFeedFilters,
+ type ImageFeedDBModel
+} from "@/utils";
import { getImageFeedById, getMyImageFeeds } from "../backend.remote";
import { command, form, getRequestEvent } from "$app/server";
import { invalid, redirect } from "@sveltejs/kit";
@@ -8,159 +12,166 @@ import * as v from "valibot";
config();
const CreateImageFeedSchema = v.object({
- name: v.pipe(v.string(), v.nonEmpty("A name for the feed is required.")),
- description: v.pipe(v.string(), v.nonEmpty("A description for the feed is required."))
+ name: v.pipe(v.string(), v.nonEmpty("A name for the feed is required.")),
+ description: v.pipe(v.string(), v.nonEmpty("A description for the feed is required."))
});
export const createImageFeedCommand = command(CreateImageFeedSchema, async (newIFeed) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const imageFeeds = await getMyImageFeeds();
-
- if (imageFeeds.length > 0 && locals.user.accessLevel === "none") {
- return {
- error: true,
- msg: "Exceed the amount of image feeds on the free plan."
- }
- }
-
- try {
- let data: Partial = {
- "name": newIFeed.name,
- "description": newIFeed.description,
- "owner": locals.user.id,
- "displaySettings": defaultImageFeedCustomizations,
- "filters": defaultImageFeedFilters
- };
-
- await locals.pb.collection('imageFeeds').create(data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: "Internal Server Error."
- }
- }
-
- getMyImageFeeds().refresh();
- return {
- error: false,
- msg: "Successfully created image feed."
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const imageFeeds = await getMyImageFeeds();
+
+ if (imageFeeds.length > 0 && locals.user.accessLevel === "none") {
+ return {
+ error: true,
+ msg: "Exceed the amount of image feeds on the free plan."
+ };
+ }
+
+ try {
+ let data: Partial = {
+ name: newIFeed.name,
+ description: newIFeed.description,
+ owner: locals.user.id,
+ displaySettings: defaultImageFeedCustomizations,
+ filters: defaultImageFeedFilters
+ };
+
+ await locals.pb.collection("imageFeeds").create(data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: "Internal Server Error."
+ };
+ }
+
+ getMyImageFeeds().refresh();
+ return {
+ error: false,
+ msg: "Successfully created image feed."
+ };
});
const UpdateImageFeedSchema = v.object({
- id: v.string(),
- name: v.string(),
- description: v.string(),
- avatarLink: v.optional(v.string()),
- newAvatar: v.optional(v.any()),
- displaySettings: v.object({
- showEventExtraInfo: v.optional(v.boolean(), false),
- showEventName: v.optional(v.boolean(), false),
- showEventDescription: v.optional(v.boolean(), false),
- showEventRegistration: v.optional(v.boolean(), false),
- feedDurationMS: v.number()
- }),
- filters: v.optional(v.object({
- onlyShowFeatured: v.optional(v.boolean(), false),
- hideUnpublished: v.optional(v.boolean(), false),
- hideRecurringEvents: v.optional(v.boolean(), false)
- }), undefined)
+ id: v.string(),
+ name: v.string(),
+ description: v.string(),
+ avatarLink: v.optional(v.string()),
+ newAvatar: v.optional(v.any()),
+ displaySettings: v.object({
+ showEventExtraInfo: v.optional(v.boolean(), false),
+ showEventName: v.optional(v.boolean(), false),
+ showEventDescription: v.optional(v.boolean(), false),
+ showEventRegistration: v.optional(v.boolean(), false),
+ feedDurationMS: v.number()
+ }),
+ filters: v.optional(
+ v.object({
+ onlyShowFeatured: v.optional(v.boolean(), false),
+ hideUnpublished: v.optional(v.boolean(), false),
+ hideRecurringEvents: v.optional(v.boolean(), false)
+ }),
+ undefined
+ )
});
export const updateImageFeedForm = form(UpdateImageFeedSchema, async (updatedIFeed, issue) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return redirect(303, "/");
- }
-
- const currentImageFeed = await getImageFeedById(updatedIFeed.id);
-
- try {
- let data: Partial = {
- "name": updatedIFeed.name,
- "description": updatedIFeed.description,
- "displaySettings": updatedIFeed.displaySettings,
- "filters": updatedIFeed.filters ? updatedIFeed.filters : {
- "hideUnpublished": false,
- "onlyShowFeatured": false,
- "hideRecurringEvents": false
- }
- };
-
- if (!updatedIFeed.avatarLink && updatedIFeed.newAvatar) {
- const newAvatar = updatedIFeed.newAvatar as File;
- const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, { type: newAvatar.type });
- data["logo"] = file;
- }
-
- if (!updatedIFeed.avatarLink && !updatedIFeed.newAvatar) {
- data["logo"] = "";
- }
-
- await locals.pb.collection('imageFeeds').update(currentImageFeed.id, data, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return invalid(issue.id("Failed to update image feed."));
- }
-
- getMyImageFeeds().refresh();
- getImageFeedById(currentImageFeed.id).refresh();
- return {
- error: false,
- msg: "Successfully updated image feed."
- }
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return redirect(303, "/");
+ }
+
+ const currentImageFeed = await getImageFeedById(updatedIFeed.id);
+
+ try {
+ let data: Partial = {
+ name: updatedIFeed.name,
+ description: updatedIFeed.description,
+ displaySettings: updatedIFeed.displaySettings,
+ filters: updatedIFeed.filters
+ ? updatedIFeed.filters
+ : {
+ hideUnpublished: false,
+ onlyShowFeatured: false,
+ hideRecurringEvents: false
+ }
+ };
+
+ if (!updatedIFeed.avatarLink && updatedIFeed.newAvatar) {
+ const newAvatar = updatedIFeed.newAvatar as File;
+ const file = new File([await newAvatar.arrayBuffer()], newAvatar.name, {
+ type: newAvatar.type
+ });
+ data["logo"] = file;
+ }
+
+ if (!updatedIFeed.avatarLink && !updatedIFeed.newAvatar) {
+ data["logo"] = "";
+ }
+
+ await locals.pb.collection("imageFeeds").update(currentImageFeed.id, data, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return invalid(issue.id("Failed to update image feed."));
+ }
+
+ getMyImageFeeds().refresh();
+ getImageFeedById(currentImageFeed.id).refresh();
+ return {
+ error: false,
+ msg: "Successfully updated image feed."
+ };
});
export const deleteImageFeedCommand = command(v.string(), async (id) => {
- const { locals } = getRequestEvent();
-
- if (!locals.user) {
- return {
- error: true,
- msg: "No User."
- }
- }
-
- const imageFeed = await getImageFeedById(id);
-
- try {
- await locals.pb.collection('imageFeeds').delete(imageFeed.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(err);
-
- return {
- error: true,
- msg: `Internal Server Error.`
- }
- }
-
- getMyImageFeeds().refresh();
- return {
- error: false,
- msg: "Successfully deleted image feed."
- }
-});
\ No newline at end of file
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) {
+ return {
+ error: true,
+ msg: "No User."
+ };
+ }
+
+ const imageFeed = await getImageFeedById(id);
+
+ try {
+ await locals.pb.collection("imageFeeds").delete(imageFeed.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(err);
+
+ return {
+ error: true,
+ msg: `Internal Server Error.`
+ };
+ }
+
+ getMyImageFeeds().refresh();
+ return {
+ error: false,
+ msg: "Successfully deleted image feed."
+ };
+});
diff --git a/frontend/src/routes/(mainWebsite)/dashboard/integrations.remote.ts b/frontend/src/routes/(mainWebsite)/dashboard/integrations.remote.ts
index 60faaa2..0247a23 100644
--- a/frontend/src/routes/(mainWebsite)/dashboard/integrations.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/dashboard/integrations.remote.ts
@@ -9,102 +9,104 @@ import { getMyIntegrations } from "./backend.remote";
config();
const AddIntegrationData = v.object({
- provider: v.string()
+ provider: v.string()
});
export const addAnIntegration = form(AddIntegrationData, async (integrationData) => {
- const { locals, url, cookies } = getRequestEvent();
+ const { locals, url, cookies } = getRequestEvent();
- if (!locals.user) return redirect(303, "/");
-
- const authMethods = await locals.pb.collection('integration').listAuthMethods();
+ if (!locals.user) return redirect(303, "/");
- if (!authMethods.oauth2.enabled) return redirect(303, "/dashboard");
+ const authMethods = await locals.pb.collection("integration").listAuthMethods();
- let redirectURL;
+ if (!authMethods.oauth2.enabled) return redirect(303, "/dashboard");
- if (dev) {
- if (url.origin.includes(".dev")) {
- redirectURL = url.origin.replace("http", "https") + "/oath";
- } else {
- redirectURL = url.origin + "/oath";
- }
- } else {
- redirectURL = process.env["VITE_WEBSITE_URL"] + "/oath";
- }
+ let redirectURL;
- let authProvider: AuthProviderInfo | undefined;
- for (let i=0;i {
- const { locals } = getRequestEvent();
-
- if (!locals.user) return redirect(303, "/");
-
- try {
- const record = await locals.pb.collection('integration').getFirstListItem(`id="${integrationData.id}" && owner ="${locals.user.id}"`, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
-
- await locals.pb.collection('integration').delete(record.id, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
-
- getMyIntegrations().refresh();
- } catch (err) {
- console.log(err);
- return redirect(303, "/dashboard");
- }
-
- return { msg: "Good" }
-});
\ No newline at end of file
+ const { locals } = getRequestEvent();
+
+ if (!locals.user) return redirect(303, "/");
+
+ try {
+ const record = await locals.pb
+ .collection("integration")
+ .getFirstListItem(`id="${integrationData.id}" && owner ="${locals.user.id}"`, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+
+ await locals.pb.collection("integration").delete(record.id, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+
+ getMyIntegrations().refresh();
+ } catch (err) {
+ console.log(err);
+ return redirect(303, "/dashboard");
+ }
+
+ return { msg: "Good" };
+});
diff --git a/frontend/src/routes/(mainWebsite)/login/+page.svelte b/frontend/src/routes/(mainWebsite)/login/+page.svelte
index f53eecb..609eceb 100644
--- a/frontend/src/routes/(mainWebsite)/login/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/login/+page.svelte
@@ -1,20 +1,22 @@
- Login | InfoSections
+ Login | InfoSections
\ No newline at end of file
+
+
diff --git a/frontend/src/routes/(mainWebsite)/login/login.remote.ts b/frontend/src/routes/(mainWebsite)/login/login.remote.ts
index 51b3caf..514f1bd 100644
--- a/frontend/src/routes/(mainWebsite)/login/login.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/login/login.remote.ts
@@ -7,29 +7,27 @@ import * as v from "valibot";
config();
const EmailPasswordLoginSchema = v.object({
- email: EmailSchema,
- password: v.pipe(v.string(), v.minLength(8, "The password must be longer than 8 characters."))
+ email: EmailSchema,
+ password: v.pipe(v.string(), v.minLength(8, "The password must be longer than 8 characters."))
});
export const emailPasswordLogin = form(EmailPasswordLoginSchema, async (newSignupData, issue) => {
- const { locals, cookies } = getRequestEvent();
+ const { locals, cookies } = getRequestEvent();
- try {
- await locals.pb.collection('users').authWithPassword(
- newSignupData.email,
- newSignupData.password,
- );
+ try {
+ await locals.pb
+ .collection("users")
+ .authWithPassword(newSignupData.email, newSignupData.password);
- const authCookieString = locals.pb.authStore.exportToCookie().split(";");
- cookies.set("pb_auth", authCookieString[0] ? authCookieString[0] : "", {
- path: "/"
- })
- } catch (err) {
- console.log(err);
+ const authCookieString = locals.pb.authStore.exportToCookie().split(";");
+ cookies.set("pb_auth", authCookieString[0] ? authCookieString[0] : "", {
+ path: "/"
+ });
+ } catch (err) {
+ console.log(err);
- return invalid(issue.email("Something went wrong."));
- }
+ return invalid(issue.email("Something went wrong."));
+ }
-
- redirect(303, "/dashboard");
-});
\ No newline at end of file
+ redirect(303, "/dashboard");
+});
diff --git a/frontend/src/routes/(mainWebsite)/privacy/+page.svelte b/frontend/src/routes/(mainWebsite)/privacy/+page.svelte
index bf30f5d..d5c17d9 100644
--- a/frontend/src/routes/(mainWebsite)/privacy/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/privacy/+page.svelte
@@ -1,202 +1,188 @@
- Privacy Policy | InfoSections
+ Privacy Policy | InfoSections
-
-
-
- Back to Home
-
-
-
-
- Privacy Policy
-
-
- Last updated: March 14, 2026
-
-
-
-
- 1. Introduction
-
-
- InfoSections ("we", "our", or "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our event management platform.
-
-
-
-
-
- 2. Information We Collect
-
-
- 2.1 Information You Provide
-
-
- Account information (name, email address, password)
- Profile information and preferences
- Event data and configurations you create
- Integration credentials for third-party services
-
-
-
- 2.2 Information Collected Automatically
-
-
- Dynamic URL click tracking and referrer data
-
-
-
- 2.3 Information from Third Parties
-
-
- When you connect integrations like Planning Center, Breeze ChMS, or Google Calendar, we receive event data and related information from these services as authorized by you.
-
-
-
-
-
- 3. How We Use Your Information
-
-
- We use the information we collect to:
-
-
- Provide and maintain the Service
- Sync and display events from connected integrations
- Process Dynamic URL redirections and track referrer analytics
- Send service-related notifications and updates
- Improve and optimize the Service
- Respond to your requests and support inquiries
- Detect and prevent fraud or abuse
-
-
-
-
-
- 4. Information Sharing
-
-
- We do not sell your personal information. We may share your information in the following circumstances:
-
-
-
- Service Providers: With trusted third parties who assist us in operating the Service
-
-
- Integrations: With third-party services you connect to your account
-
-
- Legal Requirements: When required by law or to protect our rights
-
-
- Business Transfers: In connection with a merger, acquisition, or sale of assets
-
-
-
-
-
-
- 5. Data Retention
-
-
- We retain your information for as long as your account is active or as needed to provide the Service. You may request deletion of your account and associated data at any time. Some information may be retained as required by law or for legitimate business purposes.
-
-
-
-
-
- 6. Data Security
-
-
- We implement appropriate technical and organizational measures to protect your information, including encryption, secure servers, and access controls. However, no method of transmission over the Internet is 100% secure, and we cannot guarantee absolute security.
-
-
-
-
-
- 7. Your Rights
-
-
- Depending on your location, you may have the following rights:
-
-
- Access and receive a copy of your personal data
- Correct inaccurate or incomplete data
- Request deletion of your data
- Object to or restrict processing of your data
- Data portability
- Withdraw consent at any time
-
-
- To exercise these rights, please contact us at{" "}
-
- privacy@infosections.com
-
- .
-
-
-
-
-
- 8. Cookies and Tracking
-
-
- We use cookies and similar technologies to enhance your experience, analyze usage, and remember your preferences. You can control cookie settings through your browser preferences.
-
-
-
-
-
- 9. Children's Privacy
-
-
- The Service is not intended for children under 13 years of age. We do not knowingly collect personal information from children under 13. If you believe we have collected such information, please contact us immediately.
-
-
-
-
-
- 10. Changes to This Policy
-
-
- We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date.
-
-
-
-
-
- 11. Contact Us
-
-
- If you have any questions about this Privacy Policy, please contact us at:
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+ Back to Home
+
+
+
+ Privacy Policy
+ Last updated: March 14, 2026
+
+
+ 1. Introduction
+
+ InfoSections ("we", "our", or "us") is committed to protecting your privacy. This Privacy
+ Policy explains how we collect, use, disclose, and safeguard your information when you use
+ our event management platform.
+
+
+
+
+ 2. Information We Collect
+ 2.1 Information You Provide
+
+ Account information (name, email address, password)
+ Profile information and preferences
+ Event data and configurations you create
+ Integration credentials for third-party services
+
+
+
+ 2.2 Information Collected Automatically
+
+
+ Dynamic URL click tracking and referrer data
+
+
+ 2.3 Information from Third Parties
+
+ When you connect integrations like Planning Center, Breeze ChMS, or Google Calendar, we
+ receive event data and related information from these services as authorized by you.
+
+
+
+
+ 3. How We Use Your Information
+
+ We use the information we collect to:
+
+
+ Provide and maintain the Service
+ Sync and display events from connected integrations
+ Process Dynamic URL redirections and track referrer analytics
+ Send service-related notifications and updates
+ Improve and optimize the Service
+ Respond to your requests and support inquiries
+ Detect and prevent fraud or abuse
+
+
+
+
+ 4. Information Sharing
+
+ We do not sell your personal information. We may share your information in the following
+ circumstances:
+
+
+
+ Service Providers: With trusted third parties who
+ assist us in operating the Service
+
+
+ Integrations: With third-party services you connect
+ to your account
+
+
+ Legal Requirements: When required by law or to protect
+ our rights
+
+
+ Business Transfers: In connection with a merger,
+ acquisition, or sale of assets
+
+
+
+
+
+ 5. Data Retention
+
+ We retain your information for as long as your account is active or as needed to provide
+ the Service. You may request deletion of your account and associated data at any time.
+ Some information may be retained as required by law or for legitimate business purposes.
+
+
+
+
+ 6. Data Security
+
+ We implement appropriate technical and organizational measures to protect your
+ information, including encryption, secure servers, and access controls. However, no method
+ of transmission over the Internet is 100% secure, and we cannot guarantee absolute
+ security.
+
+
+
+
+ 7. Your Rights
+
+ Depending on your location, you may have the following rights:
+
+
+ Access and receive a copy of your personal data
+ Correct inaccurate or incomplete data
+ Request deletion of your data
+ Object to or restrict processing of your data
+ Data portability
+ Withdraw consent at any time
+
+
+ To exercise these rights, please contact us at{" "}
+
+ privacy@infosections.com
+
+ .
+
+
+
+
+ 8. Cookies and Tracking
+
+ We use cookies and similar technologies to enhance your experience, analyze usage, and
+ remember your preferences. You can control cookie settings through your browser
+ preferences.
+
+
+
+
+ 9. Children's Privacy
+
+ The Service is not intended for children under 13 years of age. We do not knowingly
+ collect personal information from children under 13. If you believe we have collected such
+ information, please contact us immediately.
+
+
+
+
+ 10. Changes to This Policy
+
+ We may update this Privacy Policy from time to time. We will notify you of any changes by
+ posting the new Privacy Policy on this page and updating the "Last updated" date.
+
+
+
+
+ 11. Contact Us
+
+ If you have any questions about this Privacy Policy, please contact us at:
+
+
+
+
+
+
+
+
diff --git a/frontend/src/routes/(mainWebsite)/signup/+page.svelte b/frontend/src/routes/(mainWebsite)/signup/+page.svelte
index 7fd34eb..944f8ae 100644
--- a/frontend/src/routes/(mainWebsite)/signup/+page.svelte
+++ b/frontend/src/routes/(mainWebsite)/signup/+page.svelte
@@ -1,20 +1,22 @@
- Signup | InfoSections
+ Signup | InfoSections
\ No newline at end of file
+
+
diff --git a/frontend/src/routes/(mainWebsite)/signup/signup.remote.ts b/frontend/src/routes/(mainWebsite)/signup/signup.remote.ts
index df05587..ad44ecf 100644
--- a/frontend/src/routes/(mainWebsite)/signup/signup.remote.ts
+++ b/frontend/src/routes/(mainWebsite)/signup/signup.remote.ts
@@ -10,149 +10,164 @@ import Stripe from "stripe";
config();
const CreateEmailPasswordSignupSchema = v.object({
- name: v.pipe(v.string(), v.nonEmpty("Your name is required.")),
- email: EmailSchema,
- password: v.pipe(v.string(), v.minLength(8, "The password must be longer than 8 characters.")),
- passwordConfirm: v.pipe(v.string(), v.minLength(8, "The password must be at least 8 characters long."))
+ name: v.pipe(v.string(), v.nonEmpty("Your name is required.")),
+ email: EmailSchema,
+ password: v.pipe(v.string(), v.minLength(8, "The password must be longer than 8 characters.")),
+ passwordConfirm: v.pipe(
+ v.string(),
+ v.minLength(8, "The password must be at least 8 characters long.")
+ )
});
-export const createEmailPasswordSignup = form(CreateEmailPasswordSignupSchema, async (newSignupData, issue) => {
- const { locals, cookies } = getRequestEvent();
-
- if (newSignupData.password !== newSignupData.passwordConfirm) return invalid(issue.passwordConfirm("Passwords dont match."))
-
- let existingEmailUser: RecordModel | undefined;
- try {
- existingEmailUser = await locals.pb.collection('users').getFirstListItem(`email="${newSignupData.email}"`, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- // All Good
- existingEmailUser = undefined;
- }
-
- if (existingEmailUser) return invalid(issue.email("Email already in use."));
-
- try {
- const res = await locals.pb.collection("users").create({
- name: newSignupData.name,
- password: newSignupData.password,
- passwordConfirm: newSignupData.passwordConfirm,
- email: newSignupData.email,
- accessLevel: "none"
- }, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
-
- const stripe = new Stripe(process.env["STRIPE_PRIVATE_KEY"]!);
- const customer = await stripe.customers.create({
- email: newSignupData.email,
- metadata: {
- internal_id: res.id
- },
- });
-
- const session = await stripe.checkout.sessions.create({
- line_items: [{ price: process.env["STRIPE_PRICE_ID"]!, quantity: 1 }],
- mode: 'subscription',
- success_url: process.env["VITE_WEBSITE_URL"]! + "/dashboard",
- cancel_url: process.env["VITE_WEBSITE_URL"]! + "/dashboard",
- customer: customer.id,
- });
-
- const freeTrialSession = await stripe.checkout.sessions.create({
- line_items: [{ price: process.env["STRIPE_PRICE_ID"]!, quantity: 1 }],
- mode: 'subscription',
- success_url: process.env["VITE_WEBSITE_URL"]! + "/dashboard",
- cancel_url: process.env["VITE_WEBSITE_URL"]! + "/dashboard",
- customer: customer.id,
- subscription_data: {
- trial_period_days: 14
- }
- });
-
- await locals.pb.collection("users").update(res.id, {
- customerId: customer.id,
- freeTrialURL: freeTrialSession.url,
- subscriptionURL: session.url
- }, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
-
- await locals.pb.collection('users').authWithPassword(
- newSignupData.email,
- newSignupData.password,
- );
-
- const authCookieString = locals.pb.authStore.exportToCookie().split(";");
- cookies.set("pb_auth", authCookieString[0] ? authCookieString[0] : "", {
- path: "/"
- })
- } catch (err) {
- console.log(err);
- return error(500, "Internal Error");
- }
-
- redirect(303, "/dashboard");
-});
+export const createEmailPasswordSignup = form(
+ CreateEmailPasswordSignupSchema,
+ async (newSignupData, issue) => {
+ const { locals, cookies } = getRequestEvent();
+
+ if (newSignupData.password !== newSignupData.passwordConfirm)
+ return invalid(issue.passwordConfirm("Passwords dont match."));
+
+ let existingEmailUser: RecordModel | undefined;
+ try {
+ existingEmailUser = await locals.pb
+ .collection("users")
+ .getFirstListItem(`email="${newSignupData.email}"`, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ // All Good
+ existingEmailUser = undefined;
+ }
+
+ if (existingEmailUser) return invalid(issue.email("Email already in use."));
+
+ try {
+ const res = await locals.pb.collection("users").create(
+ {
+ name: newSignupData.name,
+ password: newSignupData.password,
+ passwordConfirm: newSignupData.passwordConfirm,
+ email: newSignupData.email,
+ accessLevel: "none"
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+
+ const stripe = new Stripe(process.env["STRIPE_PRIVATE_KEY"]!);
+ const customer = await stripe.customers.create({
+ email: newSignupData.email,
+ metadata: {
+ internal_id: res.id
+ }
+ });
+
+ const session = await stripe.checkout.sessions.create({
+ line_items: [{ price: process.env["STRIPE_PRICE_ID"]!, quantity: 1 }],
+ mode: "subscription",
+ success_url: process.env["VITE_WEBSITE_URL"]! + "/dashboard",
+ cancel_url: process.env["VITE_WEBSITE_URL"]! + "/dashboard",
+ customer: customer.id
+ });
+
+ const freeTrialSession = await stripe.checkout.sessions.create({
+ line_items: [{ price: process.env["STRIPE_PRICE_ID"]!, quantity: 1 }],
+ mode: "subscription",
+ success_url: process.env["VITE_WEBSITE_URL"]! + "/dashboard",
+ cancel_url: process.env["VITE_WEBSITE_URL"]! + "/dashboard",
+ customer: customer.id,
+ subscription_data: {
+ trial_period_days: 14
+ }
+ });
+
+ await locals.pb.collection("users").update(
+ res.id,
+ {
+ customerId: customer.id,
+ freeTrialURL: freeTrialSession.url,
+ subscriptionURL: session.url
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+
+ await locals.pb
+ .collection("users")
+ .authWithPassword(newSignupData.email, newSignupData.password);
+
+ const authCookieString = locals.pb.authStore.exportToCookie().split(";");
+ cookies.set("pb_auth", authCookieString[0] ? authCookieString[0] : "", {
+ path: "/"
+ });
+ } catch (err) {
+ console.log(err);
+ return error(500, "Internal Error");
+ }
+
+ redirect(303, "/dashboard");
+ }
+);
const GoogleLoginSchema = v.object({
- id: v.string()
+ id: v.string()
});
export const googleLoginSignup = form(GoogleLoginSchema, async (_id, issue) => {
- const { locals, cookies, url } = getRequestEvent();
-
- locals.pb.authStore.clear();
- const authMethods = await locals.pb.collection('users').listAuthMethods();
-
- if (!authMethods.oauth2.enabled) {
- return {
- authProviders: '',
- }
- }
-
- let redirectURL;
-
- if (dev) {
- if (url.origin.includes(".dev")) {
- redirectURL = url.origin.replace("http", "https") + "/google_oath";
- } else {
- redirectURL = url.origin + "/google_oath";
- }
- } else {
- redirectURL = process.env["VITE_WEBSITE_URL"] + "/google_oath";
- }
-
- let authProvider: AuthProviderInfo | undefined;
-
- for (let i=0;i
- import { ArrowLeft } from "@lucide/svelte";
+ import { ArrowLeft } from "@lucide/svelte";
- Terms of Service | InfoSections
+ Terms of Service | InfoSections
-
-
-
-
- Back to Home
-
+
+
+
+
+ Back to Home
+
-
-
- Terms of Service
-
-
- Last updated: March 14, 2026
-
+
+ Terms of Service
+ Last updated: March 14, 2026
-
-
- 1. Acceptance of Terms
-
-
- By accessing or using InfoSections ("the Service"), you agree to be bound by these Terms of Service. If you do not agree to these terms, please do not use the Service.
-
-
+
+ 1. Acceptance of Terms
+
+ By accessing or using InfoSections ("the Service"), you agree to be bound by these Terms
+ of Service. If you do not agree to these terms, please do not use the Service.
+
+
-
-
- 2. Description of Service
-
-
- InfoSections provides an event management platform that allows users to fetch events from third-party services, create event displays (calendars, lists, carousels), and manage dynamic URLs for event redirection.
-
-
+
+ 2. Description of Service
+
+ InfoSections provides an event management platform that allows users to fetch events from
+ third-party services, create event displays (calendars, lists, carousels), and manage
+ dynamic URLs for event redirection.
+
+
-
-
- 3. User Accounts
-
-
- You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account. You agree to notify us immediately of any unauthorized use of your account.
-
-
+
+ 3. User Accounts
+
+ You are responsible for maintaining the confidentiality of your account credentials and
+ for all activities that occur under your account. You agree to notify us immediately of
+ any unauthorized use of your account.
+
+
-
-
- 4. Third-Party Integrations
-
-
- The Service integrates with third-party services such as Planning Center, Breeze ChMS, Google Calendar, and others. Your use of these integrations is subject to the respective third-party terms of service. We are not responsible for the availability, accuracy, or content provided by third-party services.
-
-
+
+ 4. Third-Party Integrations
+
+ The Service integrates with third-party services such as Planning Center, Breeze ChMS,
+ Google Calendar, and others. Your use of these integrations is subject to the respective
+ third-party terms of service. We are not responsible for the availability, accuracy, or
+ content provided by third-party services.
+
+
-
-
- 5. Acceptable Use
-
-
- You agree not to use the Service to:
-
-
- Violate any applicable laws or regulations
- Infringe on the rights of others
- Distribute malicious software or harmful content
- Attempt to gain unauthorized access to the Service or its systems
- Interfere with or disrupt the Service's operation
-
-
+
+ 5. Acceptable Use
+
+ You agree not to use the Service to:
+
+
+ Violate any applicable laws or regulations
+ Infringe on the rights of others
+ Distribute malicious software or harmful content
+ Attempt to gain unauthorized access to the Service or its systems
+ Interfere with or disrupt the Service's operation
+
+
-
-
- 6. Intellectual Property
-
-
- The Service and its original content, features, and functionality are owned by InfoSections and are protected by international copyright, trademark, and other intellectual property laws.
-
-
+
+ 6. Intellectual Property
+
+ The Service and its original content, features, and functionality are owned by
+ InfoSections and are protected by international copyright, trademark, and other
+ intellectual property laws.
+
+
-
-
- 7. Data and Privacy
-
-
- Your use of the Service is also governed by our{" "}
-
- Privacy Policy
-
- . By using the Service, you consent to the collection and use of information as described in the Privacy Policy.
-
-
+
+ 7. Data and Privacy
+
+ Your use of the Service is also governed by our{" "}
+ Privacy Policy
+ . By using the Service, you consent to the collection and use of information as described in
+ the Privacy Policy.
+
+
-
-
- 8. Limitation of Liability
-
-
- To the maximum extent permitted by law, InfoSections shall not be liable for any indirect, incidental, special, consequential, or punitive damages, including loss of profits, data, or other intangible losses resulting from your use of the Service.
-
-
+
+ 8. Limitation of Liability
+
+ To the maximum extent permitted by law, InfoSections shall not be liable for any indirect,
+ incidental, special, consequential, or punitive damages, including loss of profits, data,
+ or other intangible losses resulting from your use of the Service.
+
+
-
-
- 9. Termination
-
-
- We reserve the right to terminate or suspend your account and access to the Service at our sole discretion, without prior notice, for conduct that we believe violates these Terms or is harmful to other users, us, or third parties.
-
-
+
+ 9. Termination
+
+ We reserve the right to terminate or suspend your account and access to the Service at our
+ sole discretion, without prior notice, for conduct that we believe violates these Terms or
+ is harmful to other users, us, or third parties.
+
+
-
-
- 10. Changes to Terms
-
-
- We reserve the right to modify these Terms at any time. We will notify users of significant changes by posting a notice on the Service. Your continued use of the Service after such modifications constitutes acceptance of the updated Terms.
-
-
+
+ 10. Changes to Terms
+
+ We reserve the right to modify these Terms at any time. We will notify users of
+ significant changes by posting a notice on the Service. Your continued use of the Service
+ after such modifications constitutes acceptance of the updated Terms.
+
+
-
-
- 11. Contact Information
-
-
- If you have any questions about these Terms, please contact us at{" "}
-
- support@infosections.com
-
- .
-
-
-
+
+
-
-
-
\ No newline at end of file
+
+
+
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index d2bef69..39fb9cc 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -1,8 +1,8 @@
diff --git a/frontend/src/routes/api/go/[slug]/+server.ts b/frontend/src/routes/api/go/[slug]/+server.ts
index caba8d9..e4b9792 100644
--- a/frontend/src/routes/api/go/[slug]/+server.ts
+++ b/frontend/src/routes/api/go/[slug]/+server.ts
@@ -7,111 +7,117 @@ config();
//This function records the ref data after we redirect the user as to not waste their time.
async function recordRefHit(url: URL, record: DynamicURLModel, locals: App.Locals) {
- setTimeout(async () => {
- let ref = url.searchParams.get("ref");
- if (!ref || ref.length === 0) return;
-
- let newRef: URLRefHits[] = [];
- let refFound = false;
-
- for (let i=0;i {
+ let ref = url.searchParams.get("ref");
+ if (!ref || ref.length === 0) return;
+
+ let newRef: URLRefHits[] = [];
+ let refFound = false;
+
+ for (let i = 0; i < record.refs.length; i++) {
+ let currentRef = record.refs[i];
+ if (!currentRef) continue;
+
+ if (ref !== currentRef.name) {
+ newRef.push(currentRef);
+ } else {
+ newRef.push({
+ name: ref,
+ hits: currentRef.hits + 1
+ });
+ refFound = true;
+ }
+ }
+
+ if (!refFound) {
+ newRef.push({
+ name: ref,
+ hits: 1
+ });
+ }
+
+ try {
+ await locals.pb.collection("dynamic_url").update(
+ record.id,
+ {
+ refs: newRef
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ } catch (err) {
+ console.log("Failed to update ref for dynamic url: ", err);
+ }
+ }, 1);
}
export async function GET({ params, locals, url }) {
- const goID = params.slug;
-
- let record: DynamicURLModel;
- try {
- record = await locals.pb.collection('dynamic_url').getOne(goID, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (_err) {
- return error(404, "Dynamic Link not found.");
- }
-
- // The url has been disabled by the owner.
- if (record.disableURL) {
- return error(404, "Dynamic Link not found.");
- }
-
- let linkToRedirect = record.defaultRedirectTo;
-
- // If the dynamic_url owner enabled the override switch, we dont need to worry about anything else and just return the override URL
- if (record.enableOverrideRedirect) {
- linkToRedirect = record.overrideRedirectTo;
- } else if (record.enableWeekSheet) {
- let today = Temporal.Now.zonedDateTimeISO(record.timeZone);
-
- const todaysTimeSheet = record.weekSheet[today.dayOfWeek - 1];
-
- if (todaysTimeSheet) {
- const currentMinute = today.hour * 60 + today.minute;
-
- let currentTimeSheet: WeekSheetTimeSlot | null = null;
-
- for (let i=0;i
- import { page } from "$app/state";
- import { AlertCircle, RefreshCw } from "@lucide/svelte";
+ import { page } from "$app/state";
+ import { AlertCircle, RefreshCw } from "@lucide/svelte";
- {page.status} {page.error?.message}
+ {page.status} {page.error?.message}
-
-
+
+
-
{page.status} {page.error?.message}
+
{page.status} {page.error?.message}
-
- {#if page.status === 404}
- We couldn't find the calendar you're looking for. This might be due to an invalid link or the calendar may not exist.
- {:else}
- An error has occured. Please wait and try again. If the error keeps happening, contact our support team.
- {/if}
-
+
+ {#if page.status === 404}
+ We couldn't find the calendar you're looking for. This might be due to an invalid link or
+ the calendar may not exist.
+ {:else}
+ An error has occured. Please wait and try again. If the error keeps happening, contact our
+ support team.
+ {/if}
+
-
-
- Try Again
-
+
+
+ Try Again
+
-
-
-
-
\ No newline at end of file
+
+ Need help?
Contact support
+
+
+
+
diff --git a/frontend/src/routes/cal/[slug]/+layout.svelte b/frontend/src/routes/cal/[slug]/+layout.svelte
index 28b982e..914b4c5 100644
--- a/frontend/src/routes/cal/[slug]/+layout.svelte
+++ b/frontend/src/routes/cal/[slug]/+layout.svelte
@@ -2,4 +2,4 @@
let { children } = $props();
-{@render children?.()}
\ No newline at end of file
+{@render children?.()}
diff --git a/frontend/src/routes/cal/[slug]/+page.server.ts b/frontend/src/routes/cal/[slug]/+page.server.ts
index 98e2221..db16a66 100644
--- a/frontend/src/routes/cal/[slug]/+page.server.ts
+++ b/frontend/src/routes/cal/[slug]/+page.server.ts
@@ -5,42 +5,48 @@ import { config } from "dotenv";
config();
export async function load({ params, locals, cookies }) {
- let calendar: CalendarDBModel;
- try {
- calendar = await locals.pb.collection('calendars').getOne(params.slug, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Calendar not found.", err);
- return error(404, "Calendar Not Found");
- }
+ let calendar: CalendarDBModel;
+ try {
+ calendar = await locals.pb
+ .collection("calendars")
+ .getFirstListItem(`publicId="${params.slug}"`, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Calendar not found.", err);
+ return error(404, "Calendar Not Found");
+ }
- if (calendar.password && calendar.passwordEnabled) {
- if (cookies.get(`cal-${params.slug}`) !== calendar.password) {
- return redirect(307, `/cal/${params.slug}/login`);
- }
- }
+ if (calendar.password && calendar.passwordEnabled) {
+ if (cookies.get(`cal-${params.slug}`) !== calendar.password) {
+ return redirect(307, `/cal/${params.slug}/login`);
+ }
+ }
- try {
- await locals.pb.collection('calendars').update(calendar.id, {
- visits: calendar.visits + 1
- }, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to update the visits", err);
- return error(500);
- }
+ try {
+ await locals.pb.collection("calendars").update(
+ calendar.id,
+ {
+ visits: calendar.visits + 1
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ } catch (err) {
+ console.log("Failed to update the visits", err);
+ return error(500);
+ }
- return {
- id: calendar.id,
- name: calendar.name,
- logoLink: locals.pb.files.getURL(calendar, calendar.logo.toString()),
- displaySettings: calendar.displaySettings,
- filters: calendar.filters
- }
-}
\ No newline at end of file
+ return {
+ id: calendar.id,
+ name: calendar.name,
+ logoLink: locals.pb.files.getURL(calendar, calendar.logo.toString()),
+ displaySettings: calendar.displaySettings,
+ filters: calendar.filters
+ };
+}
diff --git a/frontend/src/routes/cal/[slug]/+page.svelte b/frontend/src/routes/cal/[slug]/+page.svelte
index 68dd7a1..87738da 100644
--- a/frontend/src/routes/cal/[slug]/+page.svelte
+++ b/frontend/src/routes/cal/[slug]/+page.svelte
@@ -1,77 +1,99 @@
- {data.name}
-
+ {data.name}
+
-
-
-
-
-
-
-
-
- Hide tooltip (you can still click it)
-
-
- Font scale: {fontScale}x
-
-
-
-
+
+
+
+
+
+
+
+
+ Hide tooltip (you can still click it)
+
+
+ Font scale: {fontScale}x
+
+
+
+
\ No newline at end of file
+ /* Style the thumb on hover */
+ :global(::-webkit-scrollbar-thumb:hover) {
+ background-color: #555; /* Darker gray on hover */
+ }
+
diff --git a/frontend/src/routes/cal/[slug]/eventFetch.remote.ts b/frontend/src/routes/cal/[slug]/eventFetch.remote.ts
index f8d0d7c..9f7466f 100644
--- a/frontend/src/routes/cal/[slug]/eventFetch.remote.ts
+++ b/frontend/src/routes/cal/[slug]/eventFetch.remote.ts
@@ -1,12 +1,12 @@
import {
- eventResourceAllowListToFilterString,
- eventResourceBlockListToFilterString,
- resourcesExpandRequirementsPublic,
- eventTagAllowListToFilterString,
- eventTagBlockListToFilterString,
- tagsExpandRequirementsPublic,
- eventFieldRequirementsPublic,
- type EventDBModelExpanded,
+ eventResourceAllowListToFilterString,
+ eventResourceBlockListToFilterString,
+ resourcesExpandRequirementsPublic,
+ eventTagAllowListToFilterString,
+ eventTagBlockListToFilterString,
+ tagsExpandRequirementsPublic,
+ eventFieldRequirementsPublic,
+ type EventDBModelExpanded
} from "@/event.utils";
import { getRequestEvent, query } from "$app/server";
import type { CalendarDBModel } from "@/cal.utils";
@@ -17,78 +17,82 @@ import * as v from "valibot";
config();
export const getEventsForCalendar = query(v.string(), async (calId) => {
- const { locals, cookies } = getRequestEvent();
- let events: EventDBModelExpanded[] = [];
- let calendar: CalendarDBModel;
+ const { locals, cookies } = getRequestEvent();
+ let events: EventDBModelExpanded[] = [];
+ let calendar: CalendarDBModel;
- try {
- calendar = await locals.pb.collection('calendars').getOne(calId, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Calendar not found.", err);
- return events;
- }
+ try {
+ calendar = await locals.pb.collection("calendars").getOne(calId, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Calendar not found.", err);
+ return events;
+ }
- if (calendar.password && calendar.passwordEnabled) {
- if (cookies.get(`cal-${calId}`) !== calendar.password) {
- return events;
- }
- }
+ if (calendar.password && calendar.passwordEnabled) {
+ if (cookies.get(`cal-${calId}`) !== calendar.password) {
+ return events;
+ }
+ }
- let today = Temporal.Now.zonedDateTimeISO().startOfDay();
- let monthAgo = today.subtract({ days: today.day + 7 });
- let monthAgoStr = `${monthAgo.year}-${(monthAgo.month).toString().padStart(2, '0')}-${(monthAgo.day).toString().padStart(2, '0')}`;
-
- try {
- // let filter = `startTime >= "${seventyTwoHoursAgoStr}" && startTime <= "${seventyTwoHoursLaterStr}"`;
- let filter = `startTime >= "${monthAgoStr}"`;
+ let today = Temporal.Now.zonedDateTimeISO().startOfDay();
+ let monthAgo = today.subtract({ days: today.day + 7 });
+ let monthAgoStr = `${monthAgo.year}-${monthAgo.month.toString().padStart(2, "0")}-${monthAgo.day.toString().padStart(2, "0")}`;
+ try {
+ // let filter = `startTime >= "${seventyTwoHoursAgoStr}" && startTime <= "${seventyTwoHoursLaterStr}"`;
+ let filter = `startTime >= "${monthAgoStr}"`;
- // This filter shows all events for the testing dev cal.
- if (calendar.id !== "sdinfaplylaesst") {
- filter += ` && owner="${calendar.owner}"`;
- }
+ // This filter shows all events for the testing dev cal.
+ if (calendar.id !== "sdinfaplylaesst") {
+ filter += ` && owner="${calendar.owner}"`;
+ }
- if (calendar.filters.onlyShowFeatured) {
- filter += " && featured=true"
- }
+ if (calendar.filters.onlyShowFeatured) {
+ filter += " && featured=true";
+ }
- if (calendar.filters.hideUnpublished) {
- filter += " && visibleInChurchCenter=true"
- }
+ if (calendar.filters.hideUnpublished) {
+ filter += " && visibleInChurchCenter=true";
+ }
- if (calendar.filters.enableResourceFiltering) {
- if (calendar.filters.resourceFilterType === "allow") {
- filter += eventResourceAllowListToFilterString(calendar.filters.allowResources);
- } else {
- filter += eventResourceBlockListToFilterString(calendar.filters.blockResources);
- }
- }
+ if (calendar.filters.enableResourceFiltering) {
+ if (calendar.filters.resourceFilterType === "allow") {
+ filter += eventResourceAllowListToFilterString(calendar.filters.allowResources);
+ } else {
+ filter += eventResourceBlockListToFilterString(calendar.filters.blockResources);
+ }
+ }
- if (calendar.filters.enableTagFiltering) {
- if (calendar.filters.tagFilterType === "allow") {
- filter += eventTagAllowListToFilterString(calendar.filters.allowTags);
- } else {
- filter += eventTagBlockListToFilterString(calendar.filters.blockTags);
- }
- }
+ if (calendar.filters.enableTagFiltering) {
+ if (calendar.filters.tagFilterType === "allow") {
+ filter += eventTagAllowListToFilterString(calendar.filters.allowTags);
+ } else {
+ filter += eventTagBlockListToFilterString(calendar.filters.blockTags);
+ }
+ }
- events = await locals.pb.collection('events').getFullList({
- filter,
- expand: "tags,resources",
- sort: 'startTime',
- fields: eventFieldRequirementsPublic + "," + tagsExpandRequirementsPublic + "," + resourcesExpandRequirementsPublic,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Calendar not found.");
- return events;
- }
-
- return events;
-});
\ No newline at end of file
+ events = await locals.pb.collection("events").getFullList({
+ filter,
+ expand: "tags,resources",
+ sort: "startTime",
+ fields:
+ eventFieldRequirementsPublic +
+ "," +
+ tagsExpandRequirementsPublic +
+ "," +
+ resourcesExpandRequirementsPublic,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Calendar not found.");
+ return events;
+ }
+
+ return events;
+});
diff --git a/frontend/src/routes/cal/[slug]/login/+page.server.ts b/frontend/src/routes/cal/[slug]/login/+page.server.ts
index 81ca4cf..ceb7da1 100644
--- a/frontend/src/routes/cal/[slug]/login/+page.server.ts
+++ b/frontend/src/routes/cal/[slug]/login/+page.server.ts
@@ -1,58 +1,62 @@
-import { error, fail, redirect } from '@sveltejs/kit';
-import type { CalendarDBModel } from '@/cal.utils.js';
+import { error, fail, redirect } from "@sveltejs/kit";
+import type { CalendarDBModel } from "@/cal.utils.js";
import { config } from "dotenv";
config();
export async function load({ locals, params }) {
- let calendar: CalendarDBModel;
- try {
- calendar = await locals.pb.collection('calendars').getFirstListItem(`id="${params.slug}"`, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log(`[server] Calendar not found: '${params.slug}'.`);
- return error(404, "Calendar doesn't exist.");
- }
-
- if (!calendar.password) {
- return redirect(307, `/cal/${params.slug}`);
- }
-
- return {
- name: calendar.name,
- id: calendar.id,
- logo: locals.pb.files.getURL(calendar, calendar.logo as string),
- customMessage: calendar.passwordScreenMessage
- }
+ let calendar: CalendarDBModel;
+ try {
+ calendar = await locals.pb
+ .collection("calendars")
+ .getFirstListItem(`publicId="${params.slug}"`, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log(`[server] Calendar not found: '${params.slug}'.`);
+ return error(404, "Calendar doesn't exist.");
+ }
+
+ if (!calendar.password) {
+ return redirect(307, `/cal/${params.slug}`);
+ }
+
+ return {
+ name: calendar.name,
+ id: calendar.id,
+ logo: locals.pb.files.getURL(calendar, calendar.logo as string),
+ customMessage: calendar.passwordScreenMessage
+ };
}
export const actions = {
- default: async ({ cookies, params, request, locals }) => {
- const data = await request.formData();
-
- let calendar: CalendarDBModel;
- try {
- calendar = await locals.pb.collection('calendars').getFirstListItem(`id="${params.slug}"`, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Calendar not found.");
- return error(404, "Calendar doesn't exist.");
- }
-
- if (calendar.password !== data.get("password")) {
- return fail(401, { message: "Invalid Password!" });
- }
-
- cookies.set(`cal-${params.slug}`, calendar.password, {
- path: "/"
- });
-
- return redirect(303, `/cal/${params.slug}`);
- }
-};
\ No newline at end of file
+ default: async ({ cookies, params, request, locals }) => {
+ const data = await request.formData();
+
+ let calendar: CalendarDBModel;
+ try {
+ calendar = await locals.pb
+ .collection("calendars")
+ .getFirstListItem(`publicId="${params.slug}"`, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Calendar not found.");
+ return error(404, "Calendar doesn't exist.");
+ }
+
+ if (calendar.password !== data.get("password")) {
+ return fail(401, { message: "Invalid Password!" });
+ }
+
+ cookies.set(`cal-${params.slug}`, calendar.password, {
+ path: "/"
+ });
+
+ return redirect(303, `/cal/${params.slug}`);
+ }
+};
diff --git a/frontend/src/routes/cal/[slug]/login/+page.svelte b/frontend/src/routes/cal/[slug]/login/+page.svelte
index fa016a1..8b99e55 100644
--- a/frontend/src/routes/cal/[slug]/login/+page.svelte
+++ b/frontend/src/routes/cal/[slug]/login/+page.svelte
@@ -1,75 +1,90 @@
- Login | {data.name}
-
+ Login | {data.name}
+
-
-
-
- {data.name}
- {#if data.customMessage.length > 0}{data.customMessage}{:else}This calendar is restricted.{/if}
-
-
- {
- updatingToast = toast.loading("Logging In...");
- return async ({ update }) => {
- if (updatingToast) {
- dismissToast();
- }
- update({ reset: true });
- };
- }}>
-
-
-
- Password
-
-
-
- Cancel
- Login
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+ {data.name}
+ {#if data.customMessage.length > 0}{data.customMessage}{:else}This calendar is restricted.{/if}
+
+
+ {
+ updatingToast = toast.loading("Logging In...");
+ return async ({ update }) => {
+ if (updatingToast) {
+ dismissToast();
+ }
+ update({ reset: true });
+ };
+ }}
+ >
+
+
+
+ Password
+
+
+
+ Cancel
+ Login
+
+
+
+
+
+
diff --git a/frontend/src/routes/cal/[slug]/logout/+server.ts b/frontend/src/routes/cal/[slug]/logout/+server.ts
index 4d8f551..35bd8d0 100644
--- a/frontend/src/routes/cal/[slug]/logout/+server.ts
+++ b/frontend/src/routes/cal/[slug]/logout/+server.ts
@@ -1,18 +1,17 @@
-import { redirect } from '@sveltejs/kit';
+import { redirect } from "@sveltejs/kit";
export function POST({ params, cookies }) {
- cookies.delete(`cal-${params.slug}`, {
- path: "/"
- });
+ cookies.delete(`cal-${params.slug}`, {
+ path: "/"
+ });
- throw redirect(303, `/cal/${params.slug}/login`);
+ throw redirect(303, `/cal/${params.slug}/login`);
}
export function GET({ params, cookies }) {
+ cookies.delete(`cal-${params.slug}`, {
+ path: "/"
+ });
- cookies.delete(`cal-${params.slug}`, {
- path: "/"
- });
-
- throw redirect(303, `/cal/${params.slug}/login`);
-}
\ No newline at end of file
+ throw redirect(303, `/cal/${params.slug}/login`);
+}
diff --git a/frontend/src/routes/elist/+error.svelte b/frontend/src/routes/elist/+error.svelte
index 9cc7137..71ce8f0 100644
--- a/frontend/src/routes/elist/+error.svelte
+++ b/frontend/src/routes/elist/+error.svelte
@@ -1,38 +1,52 @@
- {page.status} {page.error?.message}
+ {page.status} {page.error?.message}
-
-
+
+
-
{page.status} {page.error?.message}
+
{page.status} {page.error?.message}
-
- {#if page.status === 404}
- We couldn't find the event list you're looking for. This might be due to an invalid link or the list may not exist.
- {:else}
- An error has occured. Please wait and try again. If the error keeps happening, contact our support team.
- {/if}
-
+
+ {#if page.status === 404}
+ We couldn't find the event list you're looking for. This might be due to an invalid link or
+ the list may not exist.
+ {:else}
+ An error has occured. Please wait and try again. If the error keeps happening, contact our
+ support team.
+ {/if}
+
-
-
- Try Again
-
+
+
+ Try Again
+
-
-
-
-
\ No newline at end of file
+
+ Need help?
Contact support
+
+
+
+
diff --git a/frontend/src/routes/elist/[slug]/+page.server.ts b/frontend/src/routes/elist/[slug]/+page.server.ts
index d8449a3..60fd92f 100644
--- a/frontend/src/routes/elist/[slug]/+page.server.ts
+++ b/frontend/src/routes/elist/[slug]/+page.server.ts
@@ -1,5 +1,5 @@
export async function load({ params }) {
- return {
- id: params.slug
- };
+ return {
+ id: params.slug
+ };
}
diff --git a/frontend/src/routes/elist/[slug]/+page.svelte b/frontend/src/routes/elist/[slug]/+page.svelte
index 43287fb..8c4354e 100644
--- a/frontend/src/routes/elist/[slug]/+page.svelte
+++ b/frontend/src/routes/elist/[slug]/+page.svelte
@@ -1,98 +1,103 @@
- {elist.name} | InfoSections
-
+ {elist.name} | InfoSections
+
-
-
- {#if displaySettings.showUpcomingEventsTextAndDesc}
-
-
Upcoming Events
-
- Browse our schedule and register for events that interest you
-
-
- {/if}
-
-
- {#each events as event, index (`anEvent${event.id}`)}
-
- {/each}
-
-
+
+
+ {#if displaySettings.showUpcomingEventsTextAndDesc}
+
+
+ Upcoming Events
+
+
+ Browse our schedule and register for events that interest you
+
+
+ {/if}
+
+
+ {#each events as event, index (`anEvent${event.id}`)}
+
+ {/each}
+
+
\ No newline at end of file
+ :global(body) {
+ background: none transparent;
+ }
+
+ :global(::-webkit-scrollbar) {
+ width: 10px;
+ }
+
+ :global(::-webkit-scrollbar-track-piece) {
+ background-color: #fff;
+ }
+
+ :global(::-webkit-scrollbar-thumb) {
+ background-color: #cbcbcb;
+ outline: 2px solid #fff;
+ outline-offset: -2px;
+ border: 0.1px solid #b7b7b7;
+ }
+
+ :global(::-webkit-scrollbar-thumb:hover) {
+ background-color: #909090;
+ }
+
diff --git a/frontend/src/routes/elist/elistData.remote.ts b/frontend/src/routes/elist/elistData.remote.ts
index fca5320..0864631 100644
--- a/frontend/src/routes/elist/elistData.remote.ts
+++ b/frontend/src/routes/elist/elistData.remote.ts
@@ -7,19 +7,19 @@ import * as v from "valibot";
config();
export const getElistById = query(v.string(), async (listId) => {
- const { locals } = getRequestEvent();
+ const { locals } = getRequestEvent();
- let eventList: EventListDBModel;
- try {
- eventList = await locals.pb.collection("eventLists").getOne(listId, {
- headers: {
- Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!,
- },
- });
- } catch (err) {
- console.log("Event list not found.", err);
- return error(404, "Event list Not Found");
- }
+ let eventList: EventListDBModel;
+ try {
+ eventList = await locals.pb.collection("eventLists").getOne(listId, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Event list not found.", err);
+ return error(404, "Event list Not Found");
+ }
- return eventList;
+ return eventList;
});
diff --git a/frontend/src/routes/elist/elistEventFetch.remote.ts b/frontend/src/routes/elist/elistEventFetch.remote.ts
index 976c1fa..8fd8dea 100644
--- a/frontend/src/routes/elist/elistEventFetch.remote.ts
+++ b/frontend/src/routes/elist/elistEventFetch.remote.ts
@@ -15,8 +15,8 @@ export const getEventsForElist = query(v.string(), async (listId) => {
try {
eventList = await locals.pb.collection("eventLists").getOne(listId, {
headers: {
- Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!,
- },
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
});
} catch (err) {
console.log("Event list not found.", err);
@@ -47,8 +47,8 @@ export const getEventsForElist = query(v.string(), async (listId) => {
sort: "startTime",
fields: eventFieldRequirementsPublic,
headers: {
- Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!,
- },
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
});
} catch (err) {
console.log("Events not found.", err);
diff --git a/frontend/src/routes/elistPreview/+error.svelte b/frontend/src/routes/elistPreview/+error.svelte
index 9cc7137..71ce8f0 100644
--- a/frontend/src/routes/elistPreview/+error.svelte
+++ b/frontend/src/routes/elistPreview/+error.svelte
@@ -1,38 +1,52 @@
- {page.status} {page.error?.message}
+ {page.status} {page.error?.message}
-
-
+
+
-
{page.status} {page.error?.message}
+
{page.status} {page.error?.message}
-
- {#if page.status === 404}
- We couldn't find the event list you're looking for. This might be due to an invalid link or the list may not exist.
- {:else}
- An error has occured. Please wait and try again. If the error keeps happening, contact our support team.
- {/if}
-
+
+ {#if page.status === 404}
+ We couldn't find the event list you're looking for. This might be due to an invalid link or
+ the list may not exist.
+ {:else}
+ An error has occured. Please wait and try again. If the error keeps happening, contact our
+ support team.
+ {/if}
+
-
-
- Try Again
-
+
+
+ Try Again
+
-
-
-
-
\ No newline at end of file
+
+ Need help?
Contact support
+
+
+
+
diff --git a/frontend/src/routes/elistPreview/[slug]/+page.server.ts b/frontend/src/routes/elistPreview/[slug]/+page.server.ts
index 3ebde89..a95a40c 100644
--- a/frontend/src/routes/elistPreview/[slug]/+page.server.ts
+++ b/frontend/src/routes/elistPreview/[slug]/+page.server.ts
@@ -3,7 +3,7 @@ import { config } from "dotenv";
config();
export async function load({ params }) {
- return {
- id: params.slug
- }
-}
\ No newline at end of file
+ return {
+ id: params.slug
+ };
+}
diff --git a/frontend/src/routes/elistPreview/[slug]/+page.svelte b/frontend/src/routes/elistPreview/[slug]/+page.svelte
index 24e16d8..fcd8713 100644
--- a/frontend/src/routes/elistPreview/[slug]/+page.svelte
+++ b/frontend/src/routes/elistPreview/[slug]/+page.svelte
@@ -1,111 +1,116 @@
- {elist.name} | InfoSections
-
+ {elist.name} | InfoSections
+
-
-
- {#if displaySettings.showUpcomingEventsTextAndDesc}
-
-
Upcoming Events
-
- Browse our schedule and register for events that interest you
-
-
- {/if}
-
-
- {#each events as event, index (`anEvent${event.id}`)}
-
- {/each}
-
-
+
+
+ {#if displaySettings.showUpcomingEventsTextAndDesc}
+
+
+ Upcoming Events
+
+
+ Browse our schedule and register for events that interest you
+
+
+ {/if}
+
+
+ {#each events as event, index (`anEvent${event.id}`)}
+
+ {/each}
+
+
\ No newline at end of file
+ :global(body) {
+ background: none transparent;
+ }
+
+ :global(::-webkit-scrollbar) {
+ width: 10px;
+ }
+
+ :global(::-webkit-scrollbar-track-piece) {
+ background-color: #fff;
+ }
+
+ :global(::-webkit-scrollbar-thumb) {
+ background-color: #cbcbcb;
+ outline: 2px solid #fff;
+ outline-offset: -2px;
+ border: 0.1px solid #b7b7b7;
+ }
+
+ :global(::-webkit-scrollbar-thumb:hover) {
+ background-color: #909090;
+ }
+
diff --git a/frontend/src/routes/go/[slug]/+server.ts b/frontend/src/routes/go/[slug]/+server.ts
index 79417d2..7422495 100644
--- a/frontend/src/routes/go/[slug]/+server.ts
+++ b/frontend/src/routes/go/[slug]/+server.ts
@@ -7,111 +7,121 @@ config();
//This function records the ref data after we redirect the user as to not waste their time.
async function recordRefHit(url: URL, record: DynamicURLModel, locals: App.Locals) {
- setTimeout(async () => {
- let ref = url.searchParams.get("ref");
- if (!ref || ref.length === 0) return;
-
- let newRef: URLRefHits[] = [];
- let refFound = false;
-
- for (let i=0;i {
+ let ref = url.searchParams.get("ref");
+ if (!ref || ref.length === 0) return;
+
+ let newRef: URLRefHits[] = [];
+ let refFound = false;
+
+ for (let i = 0; i < record.refs.length; i++) {
+ let currentRef = record.refs[i];
+ if (!currentRef) continue;
+
+ if (ref !== currentRef.name) {
+ newRef.push(currentRef);
+ } else {
+ newRef.push({
+ name: ref,
+ hits: currentRef.hits + 1
+ });
+ refFound = true;
+ }
+ }
+
+ if (!refFound) {
+ newRef.push({
+ name: ref,
+ hits: 1
+ });
+ }
+
+ try {
+ await locals.pb.collection("dynamic_url").update(
+ record.id,
+ {
+ refs: newRef
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ } catch (err) {
+ console.log("Failed to update ref for dynamic url: ", err);
+ }
+ }, 1);
}
export async function GET({ params, locals, url }) {
- const goID = params.slug;
-
- let record: DynamicURLModel;
- try {
- record = await locals.pb.collection('dynamic_url').getOne(goID, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (_err) {
- return error(404, "Dynamic Link not found.");
- }
-
- // The url has been disabled by the owner.
- if (record.disableURL) {
- return error(404, "Dynamic Link not found.");
- }
-
- let linkToRedirect = record.defaultRedirectTo;
-
- // If the dynamic_url owner enabled the override switch, we dont need to worry about anything else and just return the override URL
- if (record.enableOverrideRedirect) {
- linkToRedirect = record.overrideRedirectTo;
- } else if (record.enableWeekSheet) {
- let today = Temporal.Now.zonedDateTimeISO(record.timeZone);
-
- const todaysTimeSheet = record.weekSheet[today.dayOfWeek - 1];
-
- if (todaysTimeSheet) {
- const currentMinute = today.hour * 60 + today.minute;
-
- let currentTimeSheet: WeekSheetTimeSlot | null = null;
-
- for (let i=0;i
- import { page } from "$app/state";
- import { AlertCircle, RefreshCw } from "@lucide/svelte";
+ import { page } from "$app/state";
+ import { AlertCircle, RefreshCw } from "@lucide/svelte";
- {page.status} {page.error?.message}
+ {page.status} {page.error?.message}
-
-
+
+
-
{page.status} {page.error?.message}
+
{page.status} {page.error?.message}
-
- {#if page.status === 404}
- We couldn't find the image feed you're looking for. This might be due to an invalid link or the feed may not exist.
- {:else}
- An error has occured. Please wait and try again. If the error keeps happening, contact our support team.
- {/if}
-
+
+ {#if page.status === 404}
+ We couldn't find the image feed you're looking for. This might be due to an invalid link or
+ the feed may not exist.
+ {:else}
+ An error has occured. Please wait and try again. If the error keeps happening, contact our
+ support team.
+ {/if}
+
-
-
- Try Again
-
+
+
+ Try Again
+
-
-
-
-
\ No newline at end of file
+
+ Need help?
Contact support
+
+
+
+
diff --git a/frontend/src/routes/ifeed/[slug]/+page.server.ts b/frontend/src/routes/ifeed/[slug]/+page.server.ts
index 0260835..a94d7c9 100644
--- a/frontend/src/routes/ifeed/[slug]/+page.server.ts
+++ b/frontend/src/routes/ifeed/[slug]/+page.server.ts
@@ -1,4 +1,9 @@
-import { eventFieldRequirementsPublic, resourcesExpandRequirementsPublic, tagsExpandRequirementsPublic, type EventDBModelExpanded } from "@/event.utils";
+import {
+ eventFieldRequirementsPublic,
+ resourcesExpandRequirementsPublic,
+ tagsExpandRequirementsPublic,
+ type EventDBModelExpanded
+} from "@/event.utils";
import type { CalendarCustomizations, CalendarFilters } from "@/cal.utils";
import type { CustomImageIFeedDBModel, ImageFeedDBModel } from "@/utils";
import { Temporal } from "temporal-polyfill";
@@ -8,117 +13,132 @@ import { config } from "dotenv";
config();
export async function load({ params, locals }) {
- let imageFeed: ImageFeedDBModel;
- try {
- imageFeed = await locals.pb.collection('imageFeeds').getOne(params.slug, {
- expand: "additionalCalendars",
- fields: "*,expand.additionalCalendars.displaySettings,expand.additionalCalendars.filters,expand.additionalCalendars.id",
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Image Feed not found.", err);
- return error(404, "Image Feed Not Found");
- }
-
- const additionalCalendars: {displaySettings: CalendarCustomizations, filters: CalendarFilters, id: string}[] = imageFeed.expand ? imageFeed.expand["additionalCalendars"] : [];
-
- // These variables and the for loop checks if any of the cals need to access a private event. If so, we need to change the event fetch filtering.
- let visibleInChurchCenterEventRequired = false;
- let featuredEventRequired = false;
-
- if (additionalCalendars) {
- for (let i=0;i= "${monthAgoStr}"`;
-
- // This filter shows all events for the testing dev feed.
- if (imageFeed.id !== "v7t0bmf8o0rqx5b") {
- filter += ` && owner="${imageFeed.owner}"`;
- }
-
- if (imageFeed.filters.onlyShowFeatured && !featuredEventRequired) {
- filter += " && featured=true"
- }
-
- if (imageFeed.filters.hideUnpublished && !visibleInChurchCenterEventRequired) {
- filter += " && visibleInChurchCenter=true"
- }
-
- events = await locals.pb.collection('events').getFullList({
- filter,
- expand: "tags,resources",
- sort: 'startTime',
- fields: eventFieldRequirementsPublic + "," + tagsExpandRequirementsPublic + "," + resourcesExpandRequirementsPublic,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Events not found.", err);
- return error(500, "Internal Server error.");
- }
-
- let customEvents: CustomImageIFeedDBModel[] = [];
- try {
- let filter = `picture != "" && imageFeed ~ "${imageFeed.id}"`;
-
- // This filter shows all events for the testing dev feed.
- if (imageFeed.id !== "v7t0bmf8o0rqx5b") {
- filter += ` && owner = "${imageFeed.owner}"`;
- }
-
- customEvents = await locals.pb.collection('customImageIfeed').getFullList({
- filter,
- fields: "id,recEventId,picture,registrationURL,created,updated,collectionId,showLink,linkText",
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Custom events not found.", err);
- customEvents = [];
- }
-
- try {
- await locals.pb.collection('imageFeeds').update(imageFeed.id, {
- visits: imageFeed.visits + 1
- }, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Failed to update the visits for image feeds", err);
- return error(500);
- }
-
- return {
- events,
- customEvents,
- name: imageFeed.name,
- logoLink: locals.pb.files.getURL(imageFeed, imageFeed.logo.toString()),
- displaySettings: imageFeed.displaySettings,
- description: imageFeed.description,
- apiServer: process.env["PB_URL"]!,
- additionalCalendars,
- filters: imageFeed.filters
- }
-}
\ No newline at end of file
+ let imageFeed: ImageFeedDBModel;
+ try {
+ imageFeed = await locals.pb.collection("imageFeeds").getOne(params.slug, {
+ expand: "additionalCalendars",
+ fields:
+ "*,expand.additionalCalendars.displaySettings,expand.additionalCalendars.filters,expand.additionalCalendars.id",
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Image Feed not found.", err);
+ return error(404, "Image Feed Not Found");
+ }
+
+ const additionalCalendars: {
+ displaySettings: CalendarCustomizations;
+ filters: CalendarFilters;
+ id: string;
+ }[] = imageFeed.expand ? imageFeed.expand["additionalCalendars"] : [];
+
+ // These variables and the for loop checks if any of the cals need to access a private event. If so, we need to change the event fetch filtering.
+ let visibleInChurchCenterEventRequired = false;
+ let featuredEventRequired = false;
+
+ if (additionalCalendars) {
+ for (let i = 0; i < additionalCalendars.length; i++) {
+ const currentAdditionalCal = additionalCalendars[i];
+ if (currentAdditionalCal && currentAdditionalCal.filters.onlyShowFeatured === false) {
+ featuredEventRequired = true;
+ }
+
+ if (currentAdditionalCal && currentAdditionalCal.filters.hideUnpublished === false) {
+ visibleInChurchCenterEventRequired = true;
+ }
+ }
+ }
+
+ const today = Temporal.Now.zonedDateTimeISO().startOfDay();
+ const monthAgo = today.subtract({ days: today.day + 7 });
+ const monthAgoStr = `${monthAgo.year}-${monthAgo.month.toString().padStart(2, "0")}-${monthAgo.day.toString().padStart(2, "0")}`;
+
+ let events: EventDBModelExpanded[] = [];
+ try {
+ let filter = `startTime >= "${monthAgoStr}"`;
+
+ // This filter shows all events for the testing dev feed.
+ if (imageFeed.id !== "v7t0bmf8o0rqx5b") {
+ filter += ` && owner="${imageFeed.owner}"`;
+ }
+
+ if (imageFeed.filters.onlyShowFeatured && !featuredEventRequired) {
+ filter += " && featured=true";
+ }
+
+ if (imageFeed.filters.hideUnpublished && !visibleInChurchCenterEventRequired) {
+ filter += " && visibleInChurchCenter=true";
+ }
+
+ events = await locals.pb.collection("events").getFullList({
+ filter,
+ expand: "tags,resources",
+ sort: "startTime",
+ fields:
+ eventFieldRequirementsPublic +
+ "," +
+ tagsExpandRequirementsPublic +
+ "," +
+ resourcesExpandRequirementsPublic,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Events not found.", err);
+ return error(500, "Internal Server error.");
+ }
+
+ let customEvents: CustomImageIFeedDBModel[] = [];
+ try {
+ let filter = `picture != "" && imageFeed ~ "${imageFeed.id}"`;
+
+ // This filter shows all events for the testing dev feed.
+ if (imageFeed.id !== "v7t0bmf8o0rqx5b") {
+ filter += ` && owner = "${imageFeed.owner}"`;
+ }
+
+ customEvents = await locals.pb.collection("customImageIfeed").getFullList({
+ filter,
+ fields:
+ "id,recEventId,picture,registrationURL,created,updated,collectionId,showLink,linkText",
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Custom events not found.", err);
+ customEvents = [];
+ }
+
+ try {
+ await locals.pb.collection("imageFeeds").update(
+ imageFeed.id,
+ {
+ visits: imageFeed.visits + 1
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ } catch (err) {
+ console.log("Failed to update the visits for image feeds", err);
+ return error(500);
+ }
+
+ return {
+ events,
+ customEvents,
+ name: imageFeed.name,
+ logoLink: locals.pb.files.getURL(imageFeed, imageFeed.logo.toString()),
+ displaySettings: imageFeed.displaySettings,
+ description: imageFeed.description,
+ apiServer: process.env["PB_URL"]!,
+ additionalCalendars,
+ filters: imageFeed.filters
+ };
+}
diff --git a/frontend/src/routes/ifeed/[slug]/+page.svelte b/frontend/src/routes/ifeed/[slug]/+page.svelte
index 98c2379..f98553b 100644
--- a/frontend/src/routes/ifeed/[slug]/+page.svelte
+++ b/frontend/src/routes/ifeed/[slug]/+page.svelte
@@ -1,184 +1,224 @@
- {data.name}
-
-
+ {data.name}
+
+
-
-
- {#each events as event (`anEvent${event.id}`)}
-
-
-
- {#if data.displaySettings.showEventExtraInfo && (data.displaySettings.showEventName || (data.displaySettings.showEventDescription && event.description.length > 0) || (data.displaySettings.showEventRegistration && event.registrationURL.length !== 0))}
-
+ {/if}
+
+
+ {/each}
+ {#each data.customEvents as customImage (`anCustomImage${customImage.id}`)}
+
+
+
+ {#if data.displaySettings.showEventExtraInfo && data.displaySettings.showEventRegistration && customImage.showLink && customImage.registrationURL.length !== 0}
+
+ {/if}
+
+
+ {/each}
+ {#if data.additionalCalendars}
+ {#each data.additionalCalendars as additionalCalendar (`anAdditionalCalendar${additionalCalendar.id}`)}
+
+
+
+
+
+
+
+ {/each}
+ {/if}
+
+
\ No newline at end of file
+ .extrastuff a {
+ margin-top: 5px;
+ border: 1px solid #ffffff;
+ width: fit-content;
+ padding: 5px;
+ }
+
diff --git a/frontend/src/routes/ifeedPreview/+error.svelte b/frontend/src/routes/ifeedPreview/+error.svelte
index 0b74f5c..e98ea0a 100644
--- a/frontend/src/routes/ifeedPreview/+error.svelte
+++ b/frontend/src/routes/ifeedPreview/+error.svelte
@@ -1,38 +1,52 @@
- {page.status} {page.error?.message}
+ {page.status} {page.error?.message}
-
-
+
+
-
{page.status} {page.error?.message}
+
{page.status} {page.error?.message}
-
- {#if page.status === 404}
- We couldn't find the image feed you're looking for. This might be due to an invalid link or the feed may not exist.
- {:else}
- An error has occured. Please wait and try again. If the error keeps happening, contact our support team.
- {/if}
-
+
+ {#if page.status === 404}
+ We couldn't find the image feed you're looking for. This might be due to an invalid link or
+ the feed may not exist.
+ {:else}
+ An error has occured. Please wait and try again. If the error keeps happening, contact our
+ support team.
+ {/if}
+
-
-
- Try Again
-
+
+
+ Try Again
+
-
-
-
-
\ No newline at end of file
+
+ Need help?
Contact support
+
+
+
+
diff --git a/frontend/src/routes/ifeedPreview/[slug]/+page.server.ts b/frontend/src/routes/ifeedPreview/[slug]/+page.server.ts
index c10d60c..762cddb 100644
--- a/frontend/src/routes/ifeedPreview/[slug]/+page.server.ts
+++ b/frontend/src/routes/ifeedPreview/[slug]/+page.server.ts
@@ -1,4 +1,9 @@
-import { eventFieldRequirementsPublic, resourcesExpandRequirementsPublic, tagsExpandRequirementsPublic, type EventDBModelExpanded } from "@/event.utils";
+import {
+ eventFieldRequirementsPublic,
+ resourcesExpandRequirementsPublic,
+ tagsExpandRequirementsPublic,
+ type EventDBModelExpanded
+} from "@/event.utils";
import type { CalendarCustomizations, CalendarFilters } from "@/cal.utils";
import type { CustomImageIFeedDBModel, ImageFeedDBModel } from "@/utils";
import { Temporal } from "temporal-polyfill";
@@ -8,105 +13,115 @@ import { config } from "dotenv";
config();
export async function load({ params, locals }) {
- let imageFeed: ImageFeedDBModel;
- try {
- imageFeed = await locals.pb.collection('imageFeeds').getOne(params.slug, {
- expand: "additionalCalendars",
- fields: "*,expand.additionalCalendars.displaySettings,expand.additionalCalendars.filters,expand.additionalCalendars.id",
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Image Feed not found.", err);
- return error(404, "Image Feed Not Found");
- }
-
- const additionalCalendars: {displaySettings: CalendarCustomizations, filters: CalendarFilters, id: string}[] = imageFeed.expand ? imageFeed.expand["additionalCalendars"] : [];
-
- // These variables and the for loop checks if any of the cals need to access a private event. If so, we need to change the event fetch filtering.
- let visibleInChurchCenterEventRequired = false;
- let featuredEventRequired = false;
-
- if (additionalCalendars) {
- for (let i=0;i "${monthAgoStr}"`;
-
- // This filter shows all events for the testing dev feed.
- if (imageFeed.id !== "v7t0bmf8o0rqx5b") {
- filter += ` && owner = "${imageFeed.owner}"`;
- }
-
- if (imageFeed.filters.onlyShowFeatured && !featuredEventRequired) {
- filter += " && featured=true"
- }
-
- if (imageFeed.filters.hideUnpublished && !visibleInChurchCenterEventRequired) {
- filter += " && visibleInChurchCenter=true"
- }
-
- events = await locals.pb.collection('events').getFullList({
- filter,
- expand: "tags,resources",
- sort: 'startTime',
- fields: eventFieldRequirementsPublic + "," + tagsExpandRequirementsPublic + "," + resourcesExpandRequirementsPublic,
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Events not found.", err);
- return error(500, "Internal Server error.");
- }
-
- let customEvents: CustomImageIFeedDBModel[] = [];
- try {
- let filter = `picture != "" && imageFeed ~ "${imageFeed.id}"`;
-
- // This filter shows all events for the testing dev feed.
- if (imageFeed.id !== "v7t0bmf8o0rqx5b") {
- filter += ` && owner = "${imageFeed.owner}"`;
- }
-
- customEvents = await locals.pb.collection('customImageIfeed').getFullList({
- filter,
- fields: "id,picture,registrationURL,created,updated,collectionId,showLink,linkText",
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Custom events not found.", err);
- customEvents = [];
- }
-
- return {
- events,
- customEvents,
- name: imageFeed.name,
- logoLink: locals.pb.files.getURL(imageFeed, imageFeed.logo.toString()),
- displaySettings: imageFeed.displaySettings,
- description: imageFeed.description,
- apiServer: process.env["PB_URL"]!,
- additionalCalendars,
- filters: imageFeed.filters,
- id: params.slug
- }
-}
\ No newline at end of file
+ let imageFeed: ImageFeedDBModel;
+ try {
+ imageFeed = await locals.pb.collection("imageFeeds").getOne(params.slug, {
+ expand: "additionalCalendars",
+ fields:
+ "*,expand.additionalCalendars.displaySettings,expand.additionalCalendars.filters,expand.additionalCalendars.id",
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Image Feed not found.", err);
+ return error(404, "Image Feed Not Found");
+ }
+
+ const additionalCalendars: {
+ displaySettings: CalendarCustomizations;
+ filters: CalendarFilters;
+ id: string;
+ }[] = imageFeed.expand ? imageFeed.expand["additionalCalendars"] : [];
+
+ // These variables and the for loop checks if any of the cals need to access a private event. If so, we need to change the event fetch filtering.
+ let visibleInChurchCenterEventRequired = false;
+ let featuredEventRequired = false;
+
+ if (additionalCalendars) {
+ for (let i = 0; i < additionalCalendars.length; i++) {
+ const currentAdditionalCal = additionalCalendars[i];
+ if (currentAdditionalCal && currentAdditionalCal.filters.onlyShowFeatured === false) {
+ featuredEventRequired = true;
+ }
+
+ if (currentAdditionalCal && currentAdditionalCal.filters.hideUnpublished === false) {
+ visibleInChurchCenterEventRequired = true;
+ }
+ }
+ }
+
+ const today = Temporal.Now.zonedDateTimeISO().startOfDay();
+ const monthAgo = today.subtract({ days: today.day + 7 });
+ const monthAgoStr = `${monthAgo.year}-${monthAgo.month.toString().padStart(2, "0")}-${monthAgo.day.toString().padStart(2, "0")}`;
+
+ let events: EventDBModelExpanded[] = [];
+ try {
+ let filter = `startTime > "${monthAgoStr}"`;
+
+ // This filter shows all events for the testing dev feed.
+ if (imageFeed.id !== "v7t0bmf8o0rqx5b") {
+ filter += ` && owner = "${imageFeed.owner}"`;
+ }
+
+ if (imageFeed.filters.onlyShowFeatured && !featuredEventRequired) {
+ filter += " && featured=true";
+ }
+
+ if (imageFeed.filters.hideUnpublished && !visibleInChurchCenterEventRequired) {
+ filter += " && visibleInChurchCenter=true";
+ }
+
+ events = await locals.pb.collection("events").getFullList({
+ filter,
+ expand: "tags,resources",
+ sort: "startTime",
+ fields:
+ eventFieldRequirementsPublic +
+ "," +
+ tagsExpandRequirementsPublic +
+ "," +
+ resourcesExpandRequirementsPublic,
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Events not found.", err);
+ return error(500, "Internal Server error.");
+ }
+
+ let customEvents: CustomImageIFeedDBModel[] = [];
+ try {
+ let filter = `picture != "" && imageFeed ~ "${imageFeed.id}"`;
+
+ // This filter shows all events for the testing dev feed.
+ if (imageFeed.id !== "v7t0bmf8o0rqx5b") {
+ filter += ` && owner = "${imageFeed.owner}"`;
+ }
+
+ customEvents = await locals.pb.collection("customImageIfeed").getFullList({
+ filter,
+ fields: "id,picture,registrationURL,created,updated,collectionId,showLink,linkText",
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+ } catch (err) {
+ console.log("Custom events not found.", err);
+ customEvents = [];
+ }
+
+ return {
+ events,
+ customEvents,
+ name: imageFeed.name,
+ logoLink: locals.pb.files.getURL(imageFeed, imageFeed.logo.toString()),
+ displaySettings: imageFeed.displaySettings,
+ description: imageFeed.description,
+ apiServer: process.env["PB_URL"]!,
+ additionalCalendars,
+ filters: imageFeed.filters,
+ id: params.slug
+ };
+}
diff --git a/frontend/src/routes/ifeedPreview/[slug]/+page.svelte b/frontend/src/routes/ifeedPreview/[slug]/+page.svelte
index 544c578..795d138 100644
--- a/frontend/src/routes/ifeedPreview/[slug]/+page.svelte
+++ b/frontend/src/routes/ifeedPreview/[slug]/+page.svelte
@@ -1,225 +1,263 @@
- {data.name}
-
-
+ {data.name}
+
+
- {#each events as event (`anEvent${event.id}`)}
-
-
-
- {#if displaySettings.showEventExtraInfo && (displaySettings.showEventName || (displaySettings.showEventDescription && event.description.length > 0) || (displaySettings.showEventRegistration && event.registrationURL.length !== 0))}
-
- {/if}
- {#if debugToggle}
-
-
Type: Event
-
Id: {event.id}
-
Recurring Id: {event.recEventId}
-
Name: {event.name}
-
Link href: here
-
Image href: here
-
- {/if}
-
-
- {/each}
- {#each data.customEvents as customImage (`anCustomImage${customImage.id}`)}
-
-
-
- {#if displaySettings.showEventExtraInfo && (displaySettings.showEventRegistration && customImage.showLink && customImage.registrationURL.length !== 0)}
-
- {/if}
- {#if debugToggle}
-
-
Type: Custom Image
-
Id: {customImage.id}
-
Show Link: {customImage.showLink}
-
Link href: here
-
- {/if}
-
-
- {/each}
- {#if data.additionalCalendars}
- {#each data.additionalCalendars as additionalCalendar (`anAdditionalCalendar${additionalCalendar.id}`)}
-
-
- *Looks better on bigger screens
-
-
-
- {#if debugToggle}
-
-
Type: Calendar
-
Id: {additionalCalendar.id}
-
- {/if}
-
-
- {/each}
- {/if}
+ {#each events as event (`anEvent${event.id}`)}
+
+
+
+ {#if displaySettings.showEventExtraInfo && (displaySettings.showEventName || (displaySettings.showEventDescription && event.description.length > 0) || (displaySettings.showEventRegistration && event.registrationURL.length !== 0))}
+
+ {/if}
+ {#if debugToggle}
+
+
Type: Event
+
Id: {event.id}
+
Recurring Id: {event.recEventId}
+
Name: {event.name}
+
+ Link href: here
+
+
Image href: here
+
+ {/if}
+
+
+ {/each}
+ {#each data.customEvents as customImage (`anCustomImage${customImage.id}`)}
+
+
+
+ {#if displaySettings.showEventExtraInfo && displaySettings.showEventRegistration && customImage.showLink && customImage.registrationURL.length !== 0}
+
+ {/if}
+ {#if debugToggle}
+
+
Type: Custom Image
+
Id: {customImage.id}
+
Show Link: {customImage.showLink}
+
+ Link href: here
+
+
+ {/if}
+
+
+ {/each}
+ {#if data.additionalCalendars}
+ {#each data.additionalCalendars as additionalCalendar (`anAdditionalCalendar${additionalCalendar.id}`)}
+
+
+
+ *Looks better on bigger screens
+
+
+
+
+ {#if debugToggle}
+
+
Type: Calendar
+
Id: {additionalCalendar.id}
+
+ {/if}
+
+
+ {/each}
+ {/if}
\ No newline at end of file
+ :global(*) {
+ box-sizing: border-box;
+ /* Standard property */
+ /* user-drag: none; */
+ /* WebKit (Chrome, Safari, newer Opera) */
+ -webkit-user-drag: none;
+ /* Firefox (older versions) */
+ -moz-user-drag: none;
+ /* Internet Explorer (older versions) */
+ -ms-user-drag: none;
+ -webkit-user-select: none; /* Safari */
+ user-select: none; /* Standard syntax */
+ }
+
+ :global(body) {
+ margin: 0;
+ padding: 0;
+ background: none transparent;
+ }
+
+ .extrastuff {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ left: 0;
+ top: 0;
+ background: linear-gradient(
+ 180deg,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(255, 255, 255, 0) 82%,
+ rgba(0, 0, 0, 1) 100%
+ ) !important;
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ padding: 10px;
+ }
+
+ .debugger {
+ padding: 10px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ background-color: #000000b6;
+ color: white;
+ }
+
+ .info {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ }
+
+ .extrastuff h2,
+ .extrastuff p,
+ .extrastuff a {
+ color: #ffffff;
+ font-family: "Geist", sans-serif;
+ }
+
+ .extrastuff a {
+ margin-top: 5px;
+ border: 1px solid #ffffff;
+ width: fit-content;
+ padding: 5px;
+ }
+
+ @media (width >= 350px) {
+ .bigger-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ }
+
diff --git a/frontend/src/routes/layout.css b/frontend/src/routes/layout.css
index a6feb8d..d904a30 100644
--- a/frontend/src/routes/layout.css
+++ b/frontend/src/routes/layout.css
@@ -1,4 +1,4 @@
-@import 'tailwindcss';
+@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn-svelte/tailwind.css";
@import "@fontsource-variable/inter";
@@ -53,7 +53,7 @@
.dark {
--background: oklch(18.22% 0.00002 271.152);
- --foreground: oklch(0.24 0 0);;
+ --foreground: oklch(0.24 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
@@ -88,7 +88,7 @@
}
@theme inline {
- --font-sans: 'Inter Variable', sans-serif;
+ --font-sans: "Inter Variable", sans-serif;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
diff --git a/frontend/src/routes/logout/+server.ts b/frontend/src/routes/logout/+server.ts
index 11eb8ca..a6d4e1b 100644
--- a/frontend/src/routes/logout/+server.ts
+++ b/frontend/src/routes/logout/+server.ts
@@ -1,23 +1,23 @@
-import { redirect } from '@sveltejs/kit';
+import { redirect } from "@sveltejs/kit";
export function POST({ locals, cookies }) {
- locals.pb.authStore.clear();
- locals.user = undefined;
+ locals.pb.authStore.clear();
+ locals.user = undefined;
- cookies.delete("pb_auth", {
- path: "/"
- });
+ cookies.delete("pb_auth", {
+ path: "/"
+ });
- throw redirect(303, "/");
+ throw redirect(303, "/");
}
export function GET({ locals, cookies }) {
- locals.pb.authStore.clear();
- locals.user = undefined;
+ locals.pb.authStore.clear();
+ locals.user = undefined;
- cookies.delete("pb_auth", {
- path: "/"
- });
+ cookies.delete("pb_auth", {
+ path: "/"
+ });
- throw redirect(303, "/");
-}
\ No newline at end of file
+ throw redirect(303, "/");
+}
diff --git a/frontend/src/routes/oath/+server.ts b/frontend/src/routes/oath/+server.ts
index 411492d..1d16016 100644
--- a/frontend/src/routes/oath/+server.ts
+++ b/frontend/src/routes/oath/+server.ts
@@ -7,169 +7,185 @@ import { config } from "dotenv";
config();
export async function GET({ locals, url, cookies }) {
- const expectedState = cookies.get("integrationState");
- const expectedVerifier = cookies.get("integrationVerifier");
- const expectedName = cookies.get("integrationName");
-
- if (!expectedState || !expectedVerifier || !expectedName) {
- return redirect(303, "/");
- }
-
- if (!locals.user) return redirect(303, "/");
-
- let redirectURL;
-
- if (dev) {
- if (url.origin.includes(".dev")) {
- redirectURL = url.origin.replace("http", "https") + "/oath";
- } else {
- redirectURL = url.origin + "/oath";
- }
- } else {
- redirectURL = process.env["VITE_WEBSITE_URL"] + "/oath";
- }
-
- const state = url.searchParams.get("state");
- const code = url.searchParams.get("code");
-
- if (!state || !code) {
- return redirect(303, "/");
- }
-
- let provider: AuthProviderInfo | undefined;
- try {
- const authMethods = await locals.pb.collection("integration").listAuthMethods({
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
-
- if (!authMethods.oauth2.enabled) {
- console.log("Oath not enabled.");
- return redirect(303, "/");
- }
-
- for (let i=0;i r.json());
-
- const in180Days = new Date(new Date().setDate((new Date()).getDate() + 180));
- const in1hour = new Date(new Date().setHours((new Date()).getHours() + 1));
- const in2hours = new Date(new Date().setHours((new Date()).getHours() + 2));
-
- await locals.pb.collection("integration").create({
- owner: locals.user.id,
- service: provider.name,
- refreshToken: token.refresh_token,
- accessToken: token.access_token,
- refreshTokenExpires: in180Days,
- accessTokenExpires: in2hours,
- lastEventsFetch: in1hour,
- password: "12345678",
- passwordConfirm: "12345678",
- prettyName: provider.displayName,
- status: "connected"
- }, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Error signing up with X oath", err);
- return redirect(303, "/");
- }
- } else if (provider.name === "planningcenter") {
- try {
- const token = await fetch("https://api.planningcenteronline.com/oauth/token", {
- method: "POST",
- headers: {
- "Authorization": "Basic " + Buffer.from(`${process.env["PLANNING_CENTER_CLIENT_ID"]!}:${process.env["PLANNING_CENTER_CLIENT_SECRET"]!}`).toString("base64")
- },
- body: new URLSearchParams({
- grant_type: "authorization_code",
- code,
- redirect_uri: redirectURL,
- client_id: process.env["PLANNING_CENTER_CLIENT_ID"]!,
- code_verifier: expectedVerifier
- })
- }).then(r => r.json());
-
- const in89Days = new Date(new Date().setDate((new Date()).getDate() + 89));
- const in1hour = new Date(new Date().setHours((new Date()).getHours() + 1));
- const in2hours = new Date(new Date().setHours((new Date()).getHours() + 2));
-
- await locals.pb.collection("integration").create({
- owner: locals.user.id,
- service: provider.name,
- refreshToken: token.refresh_token,
- accessToken: token.access_token,
- refreshTokenExpires: in89Days,
- accessTokenExpires: in2hours,
- lastEventsFetch: in1hour,
- password: "12345678",
- passwordConfirm: "12345678",
- prettyName: provider.displayName,
- status: "connected"
- }, {
- headers: {
- "Authorization": "Bearer " + process.env["POCKETBASE_TOKEN"]!
- }
- });
- } catch (err) {
- console.log("Error signing up with planning center oath", err);
- return redirect(303, "/");
- }
-
- await updateSpecificUserEvents();
- }
-
- return redirect(303, "/dashboard");
-}
\ No newline at end of file
+ const expectedState = cookies.get("integrationState");
+ const expectedVerifier = cookies.get("integrationVerifier");
+ const expectedName = cookies.get("integrationName");
+
+ if (!expectedState || !expectedVerifier || !expectedName) {
+ return redirect(303, "/");
+ }
+
+ if (!locals.user) return redirect(303, "/");
+
+ let redirectURL;
+
+ if (dev) {
+ if (url.origin.includes(".dev")) {
+ redirectURL = url.origin.replace("http", "https") + "/oath";
+ } else {
+ redirectURL = url.origin + "/oath";
+ }
+ } else {
+ redirectURL = process.env["VITE_WEBSITE_URL"] + "/oath";
+ }
+
+ const state = url.searchParams.get("state");
+ const code = url.searchParams.get("code");
+
+ if (!state || !code) {
+ return redirect(303, "/");
+ }
+
+ let provider: AuthProviderInfo | undefined;
+ try {
+ const authMethods = await locals.pb.collection("integration").listAuthMethods({
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+
+ if (!authMethods.oauth2.enabled) {
+ console.log("Oath not enabled.");
+ return redirect(303, "/");
+ }
+
+ for (let i = 0; i < authMethods.oauth2.providers.length; i++) {
+ const currentProvider = authMethods.oauth2.providers[i];
+ if (currentProvider && currentProvider.name === expectedName) {
+ provider = currentProvider;
+ }
+ }
+ } catch (err) {
+ console.log("List auth methods error.", err);
+ return redirect(303, "/");
+ }
+
+ if (!provider) {
+ console.log("Provider not found.");
+ return redirect(303, "/");
+ }
+
+ if (expectedState != state) {
+ console.log("Returned state does not match expected state.");
+ return redirect(303, "/");
+ }
+
+ try {
+ const record = await locals.pb
+ .collection("integration")
+ .getFirstListItem(`service="${provider.name}" && owner ="${locals.user.id}"`, {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ });
+
+ if (record) {
+ return redirect(303, "/dashboard");
+ }
+ } catch (_err) {
+ //All Good. The record doesnt exist.
+ }
+
+ if (provider.name === "twitter") {
+ try {
+ const token = await fetch("https://api.x.com/2/oauth2/token", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ Authorization:
+ "Basic " +
+ Buffer.from(
+ `${process.env["X_OATH_CLIENT_ID"]!}:${process.env["X_OATH_CLIENT_SECRET"]!}`
+ ).toString("base64")
+ },
+ body: new URLSearchParams({
+ grant_type: "authorization_code",
+ code,
+ redirect_uri: redirectURL,
+ client_id: process.env["X_OATH_CLIENT_ID"]!,
+ code_verifier: expectedVerifier
+ })
+ }).then((r) => r.json());
+
+ const in180Days = new Date(new Date().setDate(new Date().getDate() + 180));
+ const in1hour = new Date(new Date().setHours(new Date().getHours() + 1));
+ const in2hours = new Date(new Date().setHours(new Date().getHours() + 2));
+
+ await locals.pb.collection("integration").create(
+ {
+ owner: locals.user.id,
+ service: provider.name,
+ refreshToken: token.refresh_token,
+ accessToken: token.access_token,
+ refreshTokenExpires: in180Days,
+ accessTokenExpires: in2hours,
+ lastEventsFetch: in1hour,
+ password: "12345678",
+ passwordConfirm: "12345678",
+ prettyName: provider.displayName,
+ status: "connected"
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ } catch (err) {
+ console.log("Error signing up with X oath", err);
+ return redirect(303, "/");
+ }
+ } else if (provider.name === "planningcenter") {
+ try {
+ const token = await fetch("https://api.planningcenteronline.com/oauth/token", {
+ method: "POST",
+ headers: {
+ Authorization:
+ "Basic " +
+ Buffer.from(
+ `${process.env["PLANNING_CENTER_CLIENT_ID"]!}:${process.env["PLANNING_CENTER_CLIENT_SECRET"]!}`
+ ).toString("base64")
+ },
+ body: new URLSearchParams({
+ grant_type: "authorization_code",
+ code,
+ redirect_uri: redirectURL,
+ client_id: process.env["PLANNING_CENTER_CLIENT_ID"]!,
+ code_verifier: expectedVerifier
+ })
+ }).then((r) => r.json());
+
+ const in89Days = new Date(new Date().setDate(new Date().getDate() + 89));
+ const in1hour = new Date(new Date().setHours(new Date().getHours() + 1));
+ const in2hours = new Date(new Date().setHours(new Date().getHours() + 2));
+
+ await locals.pb.collection("integration").create(
+ {
+ owner: locals.user.id,
+ service: provider.name,
+ refreshToken: token.refresh_token,
+ accessToken: token.access_token,
+ refreshTokenExpires: in89Days,
+ accessTokenExpires: in2hours,
+ lastEventsFetch: in1hour,
+ password: "12345678",
+ passwordConfirm: "12345678",
+ prettyName: provider.displayName,
+ status: "connected"
+ },
+ {
+ headers: {
+ Authorization: "Bearer " + process.env["POCKETBASE_TOKEN"]!
+ }
+ }
+ );
+ } catch (err) {
+ console.log("Error signing up with planning center oath", err);
+ return redirect(303, "/");
+ }
+
+ await updateSpecificUserEvents();
+ }
+
+ return redirect(303, "/dashboard");
+}
diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js
index 5c034b2..f6196cf 100644
--- a/frontend/svelte.config.js
+++ b/frontend/svelte.config.js
@@ -1,5 +1,5 @@
-import adapter from "svelte-adapter-bun";
-import { relative, sep } from 'node:path';
+import adapter from "svelte-adapter-bun";
+import { relative, sep } from "node:path";
/** @type {import('@sveltejs/kit').Config} */
const config = {
@@ -8,7 +8,7 @@ const config = {
runes: ({ filename }) => {
const relativePath = relative(import.meta.dirname, filename);
const pathSegments = relativePath.toLowerCase().split(sep);
- const isExternalLibrary = pathSegments.includes('node_modules');
+ const isExternalLibrary = pathSegments.includes("node_modules");
return isExternalLibrary ? undefined : true;
}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 029a779..e8d0c86 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -22,7 +22,7 @@
"noPropertyAccessFromIndexSignature": true,
"erasableSyntaxOnly": true,
"noImplicitOverride": true,
- "noErrorTruncation": true,
+ "noErrorTruncation": true
// "exactOptionalPropertyTypes": true
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 6aed00e..55dd80a 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,12 +1,10 @@
-import tailwindcss from '@tailwindcss/vite';
-import { sveltekit } from '@sveltejs/kit/vite';
-import { defineConfig } from 'vite';
+import tailwindcss from "@tailwindcss/vite";
+import { sveltekit } from "@sveltejs/kit/vite";
+import { defineConfig } from "vite";
export default defineConfig({
- plugins: [tailwindcss(), sveltekit()],
- server: {
- allowedHosts: [
- "caldev.stephengruzin.dev"
- ]
- }
+ plugins: [tailwindcss(), sveltekit()],
+ server: {
+ allowedHosts: ["caldev.stephengruzin.dev"]
+ }
});