diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c182e62544..60d29ad042 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -27,6 +27,14 @@ Thank you for your interest in contributing to 0.email! We're excited to have yo - Click the 'Fork' button at the top right of this repository - Clone your fork locally: `git clone https://github.com/YOUR-USERNAME/Zero.git` + - Next, add an `upstream` [remote](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) to sync this repository with your local fork. + + ```bash + # HTTPS + git remote add upstream https://github.com/Mail-0/Zero.git + # or SSH + git remote add upstream git@github.com:Mail-0/Zero.git + ``` 2. **Set Up Development Environment** - Install [pnpm](https://pnpm.io) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 910ad617e4..2a73b78402 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -61,7 +61,7 @@ For changes involving data or authentication: ## Checklist -- [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document +- [ ] I have read the [CONTRIBUTING](https://github.com/Mail-0/Zero/blob/staging/.github/CONTRIBUTING.md) document - [ ] My code follows the project's style guidelines - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in complex areas diff --git a/README.md b/README.md index a922f92f38..dbd458a7e4 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,16 @@ Zero is built with modern and reliable technologies: ## Getting Started +### Video Tutorial + +Watch this helpful video tutorial on how to set up Zero locally: + +

+ + Zero Setup Tutorial + +

+ ### Prerequisites **Required Versions:** @@ -168,9 +178,9 @@ You can set up Zero in two ways: 3. **Autumn Setup** (Required for some encryption) - -Go to [Autumn](https://useautumn.com/) - -For Local Use, click [onboarding](https://app.useautumn.com/sandbox/onboarding) button and generate an Autumn Secret Key - -For production, select the production mode from upper left corner and generate an fill the other fields. After that, generate an Autumn Secret Key + - Go to [Autumn](https://useautumn.com/) + - For Local Use, click [onboarding](https://app.useautumn.com/sandbox/onboarding) button and generate an Autumn Secret Key + - For production, select the production mode from upper left corner and generate and fill the other fields. After that, generate an Autumn Secret Key - Add to `.env`: @@ -178,6 +188,23 @@ You can set up Zero in two ways: AUTUMN_SECRET_KEY=your_autumn_secret ``` +4. **Twilio Setup** (Required for SMS Integration) + + - Go to the [Twilio](https://www.twilio.com/) + - Create a Twilio account if you don’t already have one + - From the dashboard, locate your: + - Account SID + - Auth Token + - Phone Number + + - Add to your `.env` file: + + ```env + TWILIO_ACCOUNT_SID=your_account_sid + TWILIO_AUTH_TOKEN=your_auth_token + TWILIO_PHONE_NUMBER=your_twilio_phone_number + ``` + ### Environment Variables Run `pnpm nizzy env` to setup your environment variables. It will copy the `.env.example` file to `.env` and fill in the variables for you. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..9a27640904 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security Policy + +## Supported Versions + +Currently supported, maintained and updated versions: + +| Version | Supported | Support Status | +| ------- | ------------------ | ------------------------------------- | +| 4.x | :white_check_mark: | Active Development & Security Updates | +| < 4.0 | :x: | End of Life (no security updates) | + +## Security Updates + +We take security seriously. Security updates are released as soon as possible after a vulnerability is discovered and verified. + +## Reporting a Vulnerability + +If you discover a security vulnerability, please follow these steps: + +1. **DO NOT** disclose the vulnerability publicly. +2. Send a detailed report to: `cto@0.email`. +3. Include in your report: + - A description of the vulnerability + - Steps to reproduce the issue + - Potential impact diff --git a/apps/mail/app/(full-width)/about.tsx b/apps/mail/app/(full-width)/about.tsx index 3cb5575de0..97162f5539 100644 --- a/apps/mail/app/(full-width)/about.tsx +++ b/apps/mail/app/(full-width)/about.tsx @@ -10,7 +10,7 @@ export default function AboutPage() {
-
+
+
+
+ + + + {teamOverlap && ( + + + Team Overlap Window + + +

+ All team members are available from{' '} + {teamOverlap.start} to{' '} + {teamOverlap.end} in the base timezone +

+
+
+ )} + +
+ {employees.map((employee) => { + const employeeCurrentTime = formatInTimeZone(currentTime, employee.timezone, 'HH:mm:ss'); + const overlap = calculateOverlap( + userWorkingHours.startTime, + userWorkingHours.endTime, + userTimezone, + employee.startTime, + employee.endTime, + employee.timezone, + ); + + return ( + + +
+ updateEmployee(employee.id, 'name', e.target.value)} + className="font-medium" + /> + +
+
Current Time
+
{employeeCurrentTime}
+
+ updateEmployee(employee.id, 'startTime', e.target.value)} + /> + updateEmployee(employee.id, 'endTime', e.target.value)} + /> +
+ {overlap ? ( +
+
Overlap
+
+ {overlap.start}-{overlap.end} +
+
+ ) : ( +
No overlap
+ )} +
+ +
+
+
+ ); + })} +
+ + {employees.length === 0 && ( + + + No employees added yet. Add employees to see timezone comparisons and working hour + overlaps. + + + )} +
+ ); +} diff --git a/apps/mail/app/(routes)/settings/categories/page.tsx b/apps/mail/app/(routes)/settings/categories/page.tsx index 6d961f5388..ba7ae10ec1 100644 --- a/apps/mail/app/(routes)/settings/categories/page.tsx +++ b/apps/mail/app/(routes)/settings/categories/page.tsx @@ -10,7 +10,15 @@ import { useTRPC } from '@/providers/query-provider'; import { toast } from 'sonner'; import type { CategorySetting } from '@/hooks/use-categories'; import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'; +import * as Icons from '@/components/icons/icons'; import { Sparkles } from '@/components/icons/icons'; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from '@/components/ui/select'; import { Loader } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; @@ -47,7 +55,7 @@ export default function CategoriesSettingsPage() { setCategories(merged.sort((a, b) => a.order - b.order)); }, [data, defaultMailCategories]); - const handleFieldChange = (id: string, field: keyof CategorySetting, value: any) => { + const handleFieldChange = (id: string, field: keyof CategorySetting, value: string | number | boolean) => { setCategories((prev) => prev.map((cat) => (cat.id === id ? { ...cat, [field]: value } : cat)), ); @@ -70,7 +78,7 @@ export default function CategoriesSettingsPage() { try { await saveUserSettings({ categories: sortedCategories }); - queryClient.setQueryData(trpc.settings.get.queryKey(), (updater: any) => { + queryClient.setQueryData(trpc.settings.get.queryKey(), (updater) => { if (!updater) return; return { settings: { ...updater.settings, categories: sortedCategories }, @@ -134,7 +142,7 @@ export default function CategoriesSettingsPage() {
-
+
-
+
+ + +
+ +
{ + if (aliases && !data?.settings?.defaultEmailAlias) { + const primaryAlias = aliases.find((alias) => alias.primary); + if (primaryAlias) { + form.setValue('defaultEmailAlias', primaryAlias.email); + } + } + }, [aliases, data?.settings?.defaultEmailAlias, form]); + async function onSubmit(values: z.infer) { setIsSaving(true); const saved = data?.settings ? { ...data.settings } : undefined; @@ -153,18 +166,19 @@ export default function GeneralPage() { return { settings: { ...updater.settings, ...values } }; }); - await setLocaleCookie({ locale: values.language }); - const localeName = new Intl.DisplayNames([values.language], { type: 'language' }).of( - values.language, - ); - toast.success(t('common.settings.languageChanged', { locale: localeName! })); - await revalidate(); + if (saved?.language !== values.language) { + await setLocaleCookie({ locale: values.language }); + const localeName = new Intl.DisplayNames([values.language], { type: 'language' }).of( + values.language, + ); + toast.success(t('common.settings.languageChanged', { locale: localeName! })); + await revalidate(); + } toast.success(t('common.settings.saved')); } catch (error) { console.error('Failed to save settings:', error); toast.error(t('common.settings.failedToSave')); - // Revert the optimistic update queryClient.setQueryData(trpc.settings.get.queryKey(), (updater) => { if (!updater) return; return saved ? { settings: { ...updater.settings, ...saved } } : updater; @@ -187,7 +201,7 @@ export default function GeneralPage() { >
-
+
)} /> + {aliases && aliases.length > 0 && ( + ( + + + {t('pages.settings.general.defaultEmailAlias')}{' '} + + + + + + {t('pages.settings.general.defaultEmailDescription')} + + + + + + )} + /> + )}
+ ( - - {t('pages.settings.general.customPrompt')} + +
+ {t('pages.settings.general.zeroSignature')} + + {t('pages.settings.general.zeroSignatureDescription')} + +
-