Improve deployment process and add login redirect logic

Deployment improvements:
- Add template env files (.envs.example/) for documentation
- Create init-production.sh for one-time server setup
- Create build-activepieces.sh for building/deploying AP image
- Update deploy.sh with --deploy-ap flag
- Make custom-pieces-metadata.sql idempotent
- Update DEPLOYMENT.md with comprehensive instructions

Frontend:
- Redirect logged-in business owners from root domain to tenant dashboard
- Redirect logged-in users from /login to /dashboard on their tenant
- Log out customers on wrong subdomain instead of redirecting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-20 23:13:56 -05:00
parent 2a33e4cf57
commit f8d8419622
38 changed files with 2471 additions and 396 deletions

View File

@@ -0,0 +1,4 @@
{
"name": "@activepieces/piece-interfaces",
"version": "0.0.1"
}

View File

@@ -0,0 +1,50 @@
{
"name": "pieces-interfaces",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/pieces/community/interfaces/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/pieces/community/interfaces",
"tsConfig": "packages/pieces/community/interfaces/tsconfig.lib.json",
"packageJson": "packages/pieces/community/interfaces/package.json",
"main": "packages/pieces/community/interfaces/src/index.ts",
"assets": [],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": [
"^build",
"prebuild"
]
},
"publish": {
"command": "node tools/scripts/publish.mjs pieces-interfaces {args.ver} {args.tag}",
"dependsOn": [
"build"
]
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
]
},
"prebuild": {
"executor": "nx:run-commands",
"options": {
"cwd": "packages/pieces/community/interfaces",
"command": "bun install --no-save --silent"
},
"dependsOn": [
"^build"
]
}
},
"tags": []
}

View File

@@ -0,0 +1,14 @@
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
export const interfaces = createPiece({
displayName: 'Interfaces',
description: 'Create custom forms and interfaces for your workflows.',
auth: PieceAuth.None(),
categories: [PieceCategory.CORE],
minimumSupportedRelease: '0.52.0',
logoUrl: 'https://cdn.activepieces.com/pieces/interfaces.svg',
authors: ['activepieces'],
actions: [],
triggers: [],
});

View File

@@ -0,0 +1,19 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View File

@@ -5,6 +5,8 @@ import { createEventAction, findEventsAction, updateEventAction, cancelEventActi
import { listResourcesAction } from './lib/actions/list-resources';
import { listServicesAction } from './lib/actions/list-services';
import { listInactiveCustomersAction } from './lib/actions/list-inactive-customers';
import { sendEmailAction } from './lib/actions/send-email';
import { listEmailTemplatesAction } from './lib/actions/list-email-templates';
import { eventCreatedTrigger, eventUpdatedTrigger, eventCancelledTrigger, eventStatusChangedTrigger } from './lib/triggers';
import { API_URL } from './lib/common';
@@ -75,6 +77,8 @@ export const smoothSchedule = createPiece({
listResourcesAction,
listServicesAction,
listInactiveCustomersAction,
sendEmailAction,
listEmailTemplatesAction,
createCustomApiCallAction({
auth: smoothScheduleAuth,
baseUrl: (auth) => (auth as SmoothScheduleAuth)?.props?.baseUrl ?? '',

View File

@@ -0,0 +1,23 @@
import { createAction } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { smoothScheduleAuth, SmoothScheduleAuth } from '../../index';
import { makeRequest } from '../common';
export const listEmailTemplatesAction = createAction({
auth: smoothScheduleAuth,
name: 'list_email_templates',
displayName: 'List Email Templates',
description: 'Get all available email templates (system and custom)',
props: {},
async run(context) {
const auth = context.auth as SmoothScheduleAuth;
const response = await makeRequest(
auth,
HttpMethod.GET,
'/emails/templates/'
);
return response;
},
});

View File

@@ -0,0 +1,112 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { smoothScheduleAuth, SmoothScheduleAuth } from '../../index';
import { makeRequest } from '../common';
export const sendEmailAction = createAction({
auth: smoothScheduleAuth,
name: 'send_email',
displayName: 'Send Email',
description: 'Send an email using a SmoothSchedule email template',
props: {
template_type: Property.StaticDropdown({
displayName: 'Template Type',
description: 'Choose whether to use a system template or a custom template',
required: true,
options: {
options: [
{ label: 'System Template', value: 'system' },
{ label: 'Custom Template', value: 'custom' },
],
},
}),
email_type: Property.StaticDropdown({
displayName: 'System Email Type',
description: 'Select a system email template',
required: false,
options: {
options: [
{ label: 'Appointment Confirmation', value: 'appointment_confirmation' },
{ label: 'Appointment Reminder', value: 'appointment_reminder' },
{ label: 'Appointment Rescheduled', value: 'appointment_rescheduled' },
{ label: 'Appointment Cancelled', value: 'appointment_cancelled' },
{ label: 'Welcome Email', value: 'welcome_email' },
{ label: 'Password Reset', value: 'password_reset' },
{ label: 'Invoice', value: 'invoice' },
{ label: 'Payment Receipt', value: 'payment_receipt' },
{ label: 'Staff Invitation', value: 'staff_invitation' },
{ label: 'Customer Winback', value: 'customer_winback' },
],
},
}),
template_slug: Property.ShortText({
displayName: 'Custom Template Slug',
description: 'The slug/identifier of your custom email template',
required: false,
}),
to_email: Property.ShortText({
displayName: 'Recipient Email',
description: 'The email address to send to',
required: true,
}),
subject_override: Property.ShortText({
displayName: 'Subject Override',
description: 'Override the template subject (optional)',
required: false,
}),
reply_to: Property.ShortText({
displayName: 'Reply-To Email',
description: 'Reply-to email address (optional)',
required: false,
}),
context: Property.Object({
displayName: 'Template Variables',
description: 'Variables to replace in the template (e.g., customer_name, appointment_date)',
required: false,
}),
},
async run(context) {
const { template_type, email_type, template_slug, to_email, subject_override, reply_to, context: templateContext } = context.propsValue;
const auth = context.auth as SmoothScheduleAuth;
// Validate that the right template identifier is provided based on type
if (template_type === 'system' && !email_type) {
throw new Error('System Email Type is required when using System Template');
}
if (template_type === 'custom' && !template_slug) {
throw new Error('Custom Template Slug is required when using Custom Template');
}
// Build the request body
const requestBody: Record<string, unknown> = {
to_email,
};
if (template_type === 'system') {
requestBody['email_type'] = email_type;
} else {
requestBody['template_slug'] = template_slug;
}
if (subject_override) {
requestBody['subject_override'] = subject_override;
}
if (reply_to) {
requestBody['reply_to'] = reply_to;
}
if (templateContext && Object.keys(templateContext).length > 0) {
requestBody['context'] = templateContext;
}
const response = await makeRequest(
auth,
HttpMethod.POST,
'/emails/send/',
requestBody
);
return response;
},
});