first commit
This commit is contained in:
34
emails/components/email-footer.tsx
Normal file
34
emails/components/email-footer.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Hr, Link, Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
|
||||
export const EmailFooter = () => {
|
||||
return (
|
||||
<>
|
||||
<Hr style={hr} />
|
||||
<Section style={footer}>
|
||||
<Text style={footerText}>
|
||||
This email was sent from Location Tracker.
|
||||
</Text>
|
||||
<Text style={footerText}>
|
||||
If you have questions, please contact your administrator.
|
||||
</Text>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const hr = {
|
||||
borderColor: '#eaeaea',
|
||||
margin: '26px 0',
|
||||
};
|
||||
|
||||
const footer = {
|
||||
padding: '0 40px',
|
||||
};
|
||||
|
||||
const footerText = {
|
||||
color: '#6b7280',
|
||||
fontSize: '12px',
|
||||
lineHeight: '1.5',
|
||||
margin: '0 0 8px',
|
||||
};
|
||||
34
emails/components/email-header.tsx
Normal file
34
emails/components/email-header.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Heading, Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
|
||||
interface EmailHeaderProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const EmailHeader = ({ title }: EmailHeaderProps) => {
|
||||
return (
|
||||
<Section style={header}>
|
||||
<Heading style={h1}>{title}</Heading>
|
||||
<Text style={subtitle}>Location Tracker</Text>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const header = {
|
||||
padding: '20px 40px',
|
||||
borderBottom: '1px solid #eaeaea',
|
||||
};
|
||||
|
||||
const h1 = {
|
||||
color: '#1f2937',
|
||||
fontSize: '24px',
|
||||
fontWeight: '600',
|
||||
lineHeight: '1.3',
|
||||
margin: '0 0 8px',
|
||||
};
|
||||
|
||||
const subtitle = {
|
||||
color: '#6b7280',
|
||||
fontSize: '14px',
|
||||
margin: '0',
|
||||
};
|
||||
40
emails/components/email-layout.tsx
Normal file
40
emails/components/email-layout.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Body,
|
||||
Container,
|
||||
Head,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
|
||||
interface EmailLayoutProps {
|
||||
preview: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const EmailLayout = ({ preview, children }: EmailLayoutProps) => {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{preview}</Preview>
|
||||
<Body style={main}>
|
||||
<Container style={container}>{children}</Container>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
const main = {
|
||||
backgroundColor: '#f6f9fc',
|
||||
fontFamily:
|
||||
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
|
||||
};
|
||||
|
||||
const container = {
|
||||
backgroundColor: '#ffffff',
|
||||
margin: '0 auto',
|
||||
padding: '20px 0 48px',
|
||||
marginBottom: '64px',
|
||||
maxWidth: '600px',
|
||||
};
|
||||
171
emails/mqtt-credentials.tsx
Normal file
171
emails/mqtt-credentials.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { EmailLayout } from './components/email-layout';
|
||||
import { EmailHeader } from './components/email-header';
|
||||
import { EmailFooter } from './components/email-footer';
|
||||
|
||||
interface MqttCredentialsEmailProps {
|
||||
deviceName: string;
|
||||
deviceId: string;
|
||||
mqttUsername: string;
|
||||
mqttPassword: string;
|
||||
brokerUrl: string;
|
||||
brokerHost?: string;
|
||||
brokerPort?: string;
|
||||
}
|
||||
|
||||
export const MqttCredentialsEmail = ({
|
||||
deviceName = 'Device',
|
||||
deviceId = '10',
|
||||
mqttUsername = 'user_device10',
|
||||
mqttPassword = 'password123',
|
||||
brokerUrl = 'mqtt://localhost:1883',
|
||||
brokerHost = 'localhost',
|
||||
brokerPort = '1883',
|
||||
}: MqttCredentialsEmailProps) => {
|
||||
return (
|
||||
<EmailLayout preview="MQTT Device Credentials">
|
||||
<EmailHeader title="MQTT Device Credentials" />
|
||||
|
||||
<Section style={content}>
|
||||
<Text style={paragraph}>
|
||||
Your MQTT credentials for device <strong>{deviceName}</strong> (ID: {deviceId}):
|
||||
</Text>
|
||||
|
||||
<Section style={credentialsBox}>
|
||||
<Text style={credentialLabel}>MQTT Broker:</Text>
|
||||
<Text style={credentialValue}>{brokerUrl}</Text>
|
||||
|
||||
<Text style={credentialLabel}>Host:</Text>
|
||||
<Text style={credentialValue}>{brokerHost}</Text>
|
||||
|
||||
<Text style={credentialLabel}>Port:</Text>
|
||||
<Text style={credentialValue}>{brokerPort}</Text>
|
||||
|
||||
<Text style={credentialLabel}>Username:</Text>
|
||||
<Text style={credentialValue}>{mqttUsername}</Text>
|
||||
|
||||
<Text style={credentialLabel}>Password:</Text>
|
||||
<Text style={credentialValue}>{mqttPassword}</Text>
|
||||
|
||||
<Text style={credentialLabel}>Topic Pattern:</Text>
|
||||
<Text style={credentialValue}>owntracks/owntrack/{deviceId}</Text>
|
||||
</Section>
|
||||
|
||||
<Section style={instructionsBox}>
|
||||
<Text style={instructionsTitle}>OwnTracks App Setup:</Text>
|
||||
|
||||
<Text style={instructionStep}>1. Open OwnTracks app</Text>
|
||||
<Text style={instructionStep}>2. Go to Settings → Connection</Text>
|
||||
<Text style={instructionStep}>3. Set Mode to "MQTT"</Text>
|
||||
<Text style={instructionStep}>4. Enter the credentials above:</Text>
|
||||
<Text style={instructionDetail}> • Host: {brokerHost}</Text>
|
||||
<Text style={instructionDetail}> • Port: {brokerPort}</Text>
|
||||
<Text style={instructionDetail}> • Username: {mqttUsername}</Text>
|
||||
<Text style={instructionDetail}> • Password: {mqttPassword}</Text>
|
||||
<Text style={instructionDetail}> • Device ID: {deviceId}</Text>
|
||||
<Text style={instructionStep}>5. Save settings</Text>
|
||||
<Text style={instructionStep}>6. The app will connect automatically</Text>
|
||||
</Section>
|
||||
|
||||
<Text style={warningText}>
|
||||
⚠️ Keep these credentials secure. Do not share them with unauthorized persons.
|
||||
</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
If you have any questions or need assistance, please contact your administrator.
|
||||
</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
Best regards,
|
||||
<br />
|
||||
Location Tracker Team
|
||||
</Text>
|
||||
</Section>
|
||||
|
||||
<EmailFooter />
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default MqttCredentialsEmail;
|
||||
|
||||
const content = {
|
||||
padding: '20px 40px',
|
||||
};
|
||||
|
||||
const paragraph = {
|
||||
color: '#374151',
|
||||
fontSize: '16px',
|
||||
lineHeight: '1.6',
|
||||
margin: '0 0 16px',
|
||||
};
|
||||
|
||||
const credentialsBox = {
|
||||
backgroundColor: '#f9fafb',
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: '6px',
|
||||
padding: '20px',
|
||||
margin: '20px 0',
|
||||
};
|
||||
|
||||
const credentialLabel = {
|
||||
color: '#6b7280',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
margin: '12px 0 4px',
|
||||
};
|
||||
|
||||
const credentialValue = {
|
||||
backgroundColor: '#ffffff',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '4px',
|
||||
color: '#111827',
|
||||
fontSize: '14px',
|
||||
fontFamily: 'monospace',
|
||||
padding: '8px 12px',
|
||||
display: 'block',
|
||||
margin: '0 0 8px',
|
||||
};
|
||||
|
||||
const instructionsBox = {
|
||||
backgroundColor: '#eff6ff',
|
||||
border: '1px solid #bfdbfe',
|
||||
borderRadius: '6px',
|
||||
padding: '20px',
|
||||
margin: '20px 0',
|
||||
};
|
||||
|
||||
const instructionsTitle = {
|
||||
color: '#1e40af',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
margin: '0 0 12px',
|
||||
};
|
||||
|
||||
const instructionStep = {
|
||||
color: '#1e3a8a',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.8',
|
||||
margin: '4px 0',
|
||||
fontWeight: '500',
|
||||
};
|
||||
|
||||
const instructionDetail = {
|
||||
color: '#3730a3',
|
||||
fontSize: '13px',
|
||||
lineHeight: '1.6',
|
||||
margin: '2px 0',
|
||||
fontFamily: 'monospace',
|
||||
};
|
||||
|
||||
const warningText = {
|
||||
backgroundColor: '#fef3c7',
|
||||
border: '1px solid #fbbf24',
|
||||
borderRadius: '6px',
|
||||
color: '#92400e',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.6',
|
||||
margin: '20px 0',
|
||||
padding: '12px 16px',
|
||||
};
|
||||
105
emails/password-reset.tsx
Normal file
105
emails/password-reset.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Button, Link, Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { EmailLayout } from './components/email-layout';
|
||||
import { EmailHeader } from './components/email-header';
|
||||
import { EmailFooter } from './components/email-footer';
|
||||
|
||||
interface PasswordResetEmailProps {
|
||||
username: string;
|
||||
resetUrl: string;
|
||||
expiresIn?: string;
|
||||
}
|
||||
|
||||
export const PasswordResetEmail = ({
|
||||
username = 'user',
|
||||
resetUrl = 'http://localhost:3000/reset-password?token=xxx',
|
||||
expiresIn = '1 hour',
|
||||
}: PasswordResetEmailProps) => {
|
||||
return (
|
||||
<EmailLayout preview="Password Reset Request">
|
||||
<EmailHeader title="Password Reset" />
|
||||
|
||||
<Section style={content}>
|
||||
<Text style={paragraph}>Hi {username},</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
We received a request to reset your password for your Location Tracker account.
|
||||
</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
Click the button below to reset your password:
|
||||
</Text>
|
||||
|
||||
<Button style={button} href={resetUrl}>
|
||||
Reset Password
|
||||
</Button>
|
||||
|
||||
<Text style={paragraph}>
|
||||
Or copy and paste this URL into your browser:{' '}
|
||||
<Link href={resetUrl} style={link}>
|
||||
{resetUrl}
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
<Text style={warningText}>
|
||||
⚠️ This link will expire in {expiresIn}. If you didn't request this password reset, please ignore this email or contact your administrator if you have concerns.
|
||||
</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
For security reasons, this password reset link can only be used once.
|
||||
</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
Best regards,
|
||||
<br />
|
||||
Location Tracker Team
|
||||
</Text>
|
||||
</Section>
|
||||
|
||||
<EmailFooter />
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordResetEmail;
|
||||
|
||||
const content = {
|
||||
padding: '20px 40px',
|
||||
};
|
||||
|
||||
const paragraph = {
|
||||
color: '#374151',
|
||||
fontSize: '16px',
|
||||
lineHeight: '1.6',
|
||||
margin: '0 0 16px',
|
||||
};
|
||||
|
||||
const button = {
|
||||
backgroundColor: '#dc2626',
|
||||
borderRadius: '6px',
|
||||
color: '#fff',
|
||||
display: 'inline-block',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
lineHeight: '1',
|
||||
padding: '12px 24px',
|
||||
textDecoration: 'none',
|
||||
textAlign: 'center' as const,
|
||||
margin: '20px 0',
|
||||
};
|
||||
|
||||
const link = {
|
||||
color: '#2563eb',
|
||||
textDecoration: 'underline',
|
||||
};
|
||||
|
||||
const warningText = {
|
||||
backgroundColor: '#fef3c7',
|
||||
border: '1px solid #fbbf24',
|
||||
borderRadius: '6px',
|
||||
color: '#92400e',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.6',
|
||||
margin: '20px 0',
|
||||
padding: '12px 16px',
|
||||
};
|
||||
106
emails/welcome.tsx
Normal file
106
emails/welcome.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Button, Link, Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { EmailLayout } from './components/email-layout';
|
||||
import { EmailHeader } from './components/email-header';
|
||||
import { EmailFooter } from './components/email-footer';
|
||||
|
||||
interface WelcomeEmailProps {
|
||||
username: string;
|
||||
loginUrl: string;
|
||||
temporaryPassword?: string;
|
||||
}
|
||||
|
||||
export const WelcomeEmail = ({
|
||||
username = 'user',
|
||||
loginUrl = 'http://localhost:3000/login',
|
||||
temporaryPassword,
|
||||
}: WelcomeEmailProps) => {
|
||||
return (
|
||||
<EmailLayout preview="Welcome to Location Tracker">
|
||||
<EmailHeader title="Welcome!" />
|
||||
|
||||
<Section style={content}>
|
||||
<Text style={paragraph}>Hi {username},</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
Welcome to Location Tracker! Your account has been created and you can now access the system.
|
||||
</Text>
|
||||
|
||||
{temporaryPassword && (
|
||||
<>
|
||||
<Text style={paragraph}>
|
||||
Your temporary password is: <strong style={code}>{temporaryPassword}</strong>
|
||||
</Text>
|
||||
<Text style={paragraph}>
|
||||
Please change this password after your first login for security.
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button style={button} href={loginUrl}>
|
||||
Login to Location Tracker
|
||||
</Button>
|
||||
|
||||
<Text style={paragraph}>
|
||||
Or copy and paste this URL into your browser:{' '}
|
||||
<Link href={loginUrl} style={link}>
|
||||
{loginUrl}
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
If you have any questions, please contact your administrator.
|
||||
</Text>
|
||||
|
||||
<Text style={paragraph}>
|
||||
Best regards,
|
||||
<br />
|
||||
Location Tracker Team
|
||||
</Text>
|
||||
</Section>
|
||||
|
||||
<EmailFooter />
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default WelcomeEmail;
|
||||
|
||||
const content = {
|
||||
padding: '20px 40px',
|
||||
};
|
||||
|
||||
const paragraph = {
|
||||
color: '#374151',
|
||||
fontSize: '16px',
|
||||
lineHeight: '1.6',
|
||||
margin: '0 0 16px',
|
||||
};
|
||||
|
||||
const button = {
|
||||
backgroundColor: '#2563eb',
|
||||
borderRadius: '6px',
|
||||
color: '#fff',
|
||||
display: 'inline-block',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
lineHeight: '1',
|
||||
padding: '12px 24px',
|
||||
textDecoration: 'none',
|
||||
textAlign: 'center' as const,
|
||||
margin: '20px 0',
|
||||
};
|
||||
|
||||
const link = {
|
||||
color: '#2563eb',
|
||||
textDecoration: 'underline',
|
||||
};
|
||||
|
||||
const code = {
|
||||
backgroundColor: '#f3f4f6',
|
||||
borderRadius: '4px',
|
||||
color: '#1f2937',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '14px',
|
||||
padding: '2px 6px',
|
||||
};
|
||||
Reference in New Issue
Block a user