1141 lines
43 KiB
Vue
1141 lines
43 KiB
Vue
<template>
|
|
<div>
|
|
<!-- Export Options - Top -->
|
|
<ExportOptions
|
|
:export-data="exportData"
|
|
filename="membership-agreement"
|
|
title="Membership Agreement" />
|
|
|
|
<div
|
|
class="template-wrapper bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100">
|
|
<!-- Document Container -->
|
|
<div class="document-page">
|
|
<div class="template-content">
|
|
<!-- Document Title -->
|
|
<div class="text-center mb-8">
|
|
<h1
|
|
class="text-3xl md:text-5xl font-bold uppercase text-neutral-900 dark:text-white m-0 py-4 border-t-2 border-b-2 border-neutral-900 dark:border-neutral-100"
|
|
:data-coop-name="
|
|
formData.cooperativeName || 'Worker Cooperative'
|
|
">
|
|
MEMBERSHIP AGREEMENT
|
|
</h1>
|
|
</div>
|
|
|
|
<!-- Section 1: Who We Are -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
1. Who We Are
|
|
</h2>
|
|
|
|
<div class="space-y-8">
|
|
<UFormField label="Cooperative Name" class="form-group-large">
|
|
<UInput
|
|
v-model="formData.cooperativeName"
|
|
placeholder="Enter cooperative name"
|
|
size="xl"
|
|
class="w-full"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<UFormField label="Date Established" class="form-group-large">
|
|
<UInput
|
|
v-model="formData.dateEstablished"
|
|
type="date"
|
|
size="xl"
|
|
class="large-field"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<UFormField label="Our Purpose" class="form-group-large">
|
|
<UTextarea
|
|
v-model="formData.purpose"
|
|
:rows="3"
|
|
placeholder="Write 1-2 sentences about what your cooperative does and why it exists"
|
|
size="xl"
|
|
class="large-field"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<UFormField label="Our Core Values" class="form-group-large">
|
|
<UTextarea
|
|
v-model="formData.coreValues"
|
|
:rows="4"
|
|
placeholder="List 3-5 values that guide everything you do. Examples: mutual care, creative sustainability, economic justice, collective liberation"
|
|
size="xl"
|
|
class="large-field"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<div class="form-group-large">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<label
|
|
class="text-lg font-semibold text-neutral-900 dark:text-neutral-100"
|
|
>Current Members</label
|
|
>
|
|
<UButton
|
|
@click="addMember"
|
|
size="sm"
|
|
color="primary"
|
|
variant="outline"
|
|
icon="i-heroicons-plus">
|
|
Add Member
|
|
</UButton>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div
|
|
v-for="(member, index) in formData.members"
|
|
:key="index"
|
|
class="border border-neutral-600 rounded-lg p-4">
|
|
<div class="flex items-start justify-between mb-3">
|
|
<h4
|
|
class="font-medium text-neutral-900 dark:text-neutral-100">
|
|
Member {{ index + 1 }}
|
|
</h4>
|
|
<UButton
|
|
v-if="formData.members.length > 1"
|
|
@click="removeMember(index)"
|
|
size="sm"
|
|
color="red"
|
|
variant="ghost"
|
|
icon="i-heroicons-trash">
|
|
</UButton>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<UFormField label="Full Name" class="form-group-large">
|
|
<UInput
|
|
v-model="member.name"
|
|
placeholder="Enter full name"
|
|
size="xl"
|
|
class="large-field"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<UFormField label="Email" class="form-group-large">
|
|
<UInput
|
|
v-model="member.email"
|
|
type="email"
|
|
placeholder="Enter email address"
|
|
size="xl"
|
|
class="large-field"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<UFormField label="Join Date" class="form-group-large">
|
|
<UInput
|
|
v-model="member.joinDate"
|
|
type="date"
|
|
size="xl"
|
|
class="large-field"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<UFormField
|
|
label="Current Role (Optional)"
|
|
class="form-group-large">
|
|
<UInput
|
|
v-model="member.role"
|
|
placeholder="e.g., Coordinator, Developer, etc."
|
|
size="xl"
|
|
class="large-field"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 2: Membership -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
2. Membership
|
|
</h2>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Who Can Be a Member
|
|
</h3>
|
|
<p class="content-paragraph mb-3 leading-relaxed text-left">
|
|
Any person who:
|
|
</p>
|
|
<UFormField label="Member Requirements" class="form-group-large">
|
|
<UTextarea
|
|
v-model="formData.memberRequirements"
|
|
:rows="4"
|
|
placeholder="Enter member requirements"
|
|
size="xl"
|
|
class="large-field"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Becoming a Member
|
|
</h3>
|
|
|
|
<p class="content-paragraph">
|
|
New members join through a consent process, which means
|
|
existing members must agree that adding this person won't harm
|
|
the cooperative.
|
|
</p>
|
|
|
|
<ol class="content-list numbered my-2 pl-6 list-decimal">
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Trial period of
|
|
<UInput
|
|
v-model="formData.trialPeriodMonths"
|
|
type="number"
|
|
placeholder="3"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
months working together
|
|
</li>
|
|
<li>Values alignment conversation</li>
|
|
<li>Consent decision by current members</li>
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Optional - Equal buy-in contribution of $<UInput
|
|
v-model="formData.buyInAmount"
|
|
type="number"
|
|
placeholder="1000"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
(can be paid over time or waived based on need)
|
|
</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Leaving the Cooperative
|
|
</h3>
|
|
|
|
<p
|
|
class="content-paragraph flex items-baseline gap-2 flex-wrap">
|
|
Members can leave anytime with
|
|
<UInput
|
|
v-model="formData.noticeDays"
|
|
type="number"
|
|
placeholder="30"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
days notice. The cooperative will:
|
|
</p>
|
|
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Pay out their share of any surplus within
|
|
<UInput
|
|
v-model="formData.surplusPayoutDays"
|
|
type="number"
|
|
placeholder="30"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
days
|
|
</li>
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Return their buy-in contribution within
|
|
<UInput
|
|
v-model="formData.buyInReturnDays"
|
|
type="number"
|
|
placeholder="90"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
days
|
|
</li>
|
|
<li>
|
|
Maintain respectful ongoing relationships when possible
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 3: How We Make Decisions -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
3. How We Make Decisions
|
|
</h2>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Consent-Based Decisions
|
|
</h3>
|
|
<p class="content-paragraph mb-3 leading-relaxed text-left">
|
|
We use consent, not consensus. This means we move forward when
|
|
no one has a principled objection that would harm the
|
|
cooperative. An objection must explain how the proposal would
|
|
contradict our values or threaten our sustainability.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Day-to-Day Decisions
|
|
</h3>
|
|
<p
|
|
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
|
|
Decisions under $<UInput
|
|
v-model="formData.dayToDayLimit"
|
|
type="number"
|
|
placeholder="100"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
can be made by any member. Just tell others what you did at
|
|
the next meeting.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Regular Decisions
|
|
</h3>
|
|
<p
|
|
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
|
|
Decisions between $<UInput
|
|
v-model="formData.regularDecisionMin"
|
|
type="number"
|
|
placeholder="100"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
and $<UInput
|
|
v-model="formData.regularDecisionMax"
|
|
type="number"
|
|
placeholder="1000"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
need consent from members present at a meeting (minimum 2
|
|
members).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Major Decisions
|
|
</h3>
|
|
<p class="content-paragraph mb-3 leading-relaxed text-left">
|
|
These require consent from all members:
|
|
</p>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li>Adding or removing members</li>
|
|
<li>Changing this agreement</li>
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Taking on debt over $<UInput
|
|
v-model="formData.majorDebtThreshold"
|
|
type="number"
|
|
placeholder="5000"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
</li>
|
|
<li>Fundamental changes to our purpose or structure</li>
|
|
<li>Dissolution of the cooperative</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Meeting Structure
|
|
</h3>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Regular meetings happen
|
|
<UInput
|
|
v-model="formData.meetingFrequency"
|
|
placeholder="weekly"
|
|
class="inline-field"
|
|
@change="autoSave" />
|
|
</li>
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Emergency meetings need
|
|
<UInput
|
|
v-model="formData.emergencyNoticeHours"
|
|
type="number"
|
|
placeholder="24"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
hours notice
|
|
</li>
|
|
<li>We rotate who facilitates meetings</li>
|
|
<li>
|
|
Decisions and reasoning get documented in shared notes
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 4: Money and Labour -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
4. Money and Labour
|
|
</h2>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Equal Ownership
|
|
</h3>
|
|
<p class="content-paragraph mb-3 leading-relaxed text-left">
|
|
Each member owns an equal share of the cooperative, regardless
|
|
of hours worked or tenure.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Paying Ourselves
|
|
</h3>
|
|
|
|
<!-- Pay Policy Selection -->
|
|
<UFormField label="Pay Policy" class="form-group-large mb-4">
|
|
<USelect
|
|
v-model="formData.payPolicy"
|
|
:items="payPolicyOptions"
|
|
placeholder="Select pay policy"
|
|
size="xl"
|
|
class="w-full"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<!-- Equal Pay Policy -->
|
|
<div v-if="formData.payPolicy === 'equal-pay'" class="space-y-3">
|
|
<p class="content-paragraph">All members receive equal compensation regardless of role or hours worked.</p>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Base rate: $<UInput
|
|
v-model="formData.baseRate"
|
|
type="number"
|
|
placeholder="25"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />/hour for all members
|
|
</li>
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Or: Equal monthly draw of $<UInput
|
|
v-model="formData.monthlyDraw"
|
|
type="number"
|
|
placeholder="2000"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
per member
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Hours-Weighted Policy -->
|
|
<div v-if="formData.payPolicy === 'hours-weighted'" class="space-y-3">
|
|
<p class="content-paragraph">Compensation is proportional to hours worked by each member.</p>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Hourly rate: $<UInput
|
|
v-model="formData.hourlyRate"
|
|
type="number"
|
|
placeholder="25"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />/hour
|
|
</li>
|
|
<li>Members track their hours and are paid accordingly</li>
|
|
<li>Minimum hours commitment may apply</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Needs-Weighted Policy -->
|
|
<div v-if="formData.payPolicy === 'needs-weighted'" class="space-y-3">
|
|
<p class="content-paragraph">Compensation is allocated based on each member's individual financial needs.</p>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li>Members declare their minimum monthly needs</li>
|
|
<li>Available payroll is distributed proportionally to cover needs</li>
|
|
<li>Regular needs assessment and adjustment process</li>
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Minimum guaranteed amount: $<UInput
|
|
v-model="formData.minGuaranteedPay"
|
|
type="number"
|
|
placeholder="1000"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />/month
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Common payment details -->
|
|
<div class="mt-4 space-y-2">
|
|
<p class="content-paragraph font-semibold">Payment Schedule:</p>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Paid on the
|
|
<USelect
|
|
v-model="formData.paymentDay"
|
|
:items="dayOptions"
|
|
placeholder="15th"
|
|
arrow
|
|
class="inline-field"
|
|
@change="autoSave" />
|
|
of each month
|
|
</li>
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Surplus (profit) distributed equally every
|
|
<UInput
|
|
v-model="formData.surplusFrequency"
|
|
placeholder="quarter"
|
|
class="inline-field"
|
|
@change="autoSave" />
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Work Expectations
|
|
</h3>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Target hours per week:
|
|
<UInput
|
|
v-model="formData.targetHours"
|
|
type="number"
|
|
placeholder="40"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
(flexible based on capacity)
|
|
</li>
|
|
<li>We explicitly reject crunch culture</li>
|
|
<li>Members communicate their capacity openly</li>
|
|
<li>
|
|
We adjust workload collectively when someone needs reduced
|
|
hours
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Financial Transparency
|
|
</h3>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li>
|
|
All members can access all financial records anytime
|
|
</li>
|
|
<li>Monthly financial check-ins at meetings</li>
|
|
<li>
|
|
Quarterly reviews of our runway (how many months we can
|
|
operate)
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 5: Roles and Responsibilities -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
5. Roles and Responsibilities
|
|
</h2>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Rotating Roles
|
|
</h3>
|
|
<p
|
|
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
|
|
We rotate operational roles every
|
|
<UInput
|
|
v-model="formData.roleRotationMonths"
|
|
type="number"
|
|
placeholder="6"
|
|
class="inline-field number-field"
|
|
@change="autoSave" />
|
|
months. Current roles include:
|
|
</p>
|
|
<UFormField label="Rotating Roles" class="form-group-large">
|
|
<UTextarea
|
|
v-model="formData.rotatingRoles"
|
|
:rows="4"
|
|
placeholder="List rotating operational roles"
|
|
size="xl"
|
|
class="large-field"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Shared Responsibilities
|
|
</h3>
|
|
<p class="content-paragraph mb-3 leading-relaxed text-left">
|
|
All members participate in:
|
|
</p>
|
|
<UFormField label="Shared Responsibilities" class="form-group-large">
|
|
<UTextarea
|
|
v-model="formData.sharedResponsibilities"
|
|
:rows="3"
|
|
placeholder="List shared responsibilities for all members"
|
|
size="xl"
|
|
class="large-field"
|
|
@input="debouncedAutoSave"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 6: Conflict and Care -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
6. Conflict and Care
|
|
</h2>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
When Conflict Happens
|
|
</h3>
|
|
<ol class="content-list numbered my-2 pl-6 list-decimal">
|
|
<li>Direct conversation between parties (if comfortable)</li>
|
|
<li>Mediation with another member</li>
|
|
<li>Full group discussion with structured process</li>
|
|
<li>External mediation if needed</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div>
|
|
<h3
|
|
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
|
|
Care Commitments
|
|
</h3>
|
|
<ul class="content-list my-2 pl-6 list-disc">
|
|
<li>We check in about capacity and wellbeing regularly</li>
|
|
<li>We honour diverse access needs</li>
|
|
<li>We maintain flexibility for life circumstances</li>
|
|
<li>
|
|
We contribute to mutual aid when members face hardship
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 7: Changing This Agreement -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
7. Changing This Agreement
|
|
</h2>
|
|
|
|
<p
|
|
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
|
|
This is a living document. We review it every
|
|
<UInput
|
|
v-model="formData.reviewFrequency"
|
|
placeholder="year"
|
|
class="inline-field"
|
|
@change="autoSave" />
|
|
and update it through our consent process. Small clarifications
|
|
can happen anytime; structural changes need full member consent.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Section 8: If We Need to Close -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
8. If We Need to Close
|
|
</h2>
|
|
|
|
<div class="space-y-4">
|
|
<p class="content-paragraph mb-3 leading-relaxed text-left">
|
|
If the cooperative dissolves:
|
|
</p>
|
|
<ol class="content-list numbered my-2 pl-6 list-decimal">
|
|
<li>Pay all debts and obligations</li>
|
|
<li>Return member contributions</li>
|
|
<li>Distribute remaining assets equally among members</li>
|
|
<li class="flex items-baseline gap-2 flex-wrap">
|
|
Or donate remaining assets to
|
|
<UInput
|
|
v-model="formData.assetDonationTarget"
|
|
placeholder="Enter organization name"
|
|
class="inline-field wide-field" />
|
|
</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section 9: Legal Bits -->
|
|
<div class="section-card">
|
|
<h2
|
|
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
|
|
9. Legal Bits
|
|
</h2>
|
|
|
|
<div class="space-y-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<UFormField label="Legal Structure" class="form-group-block">
|
|
<UInput
|
|
v-model="formData.legalStructure"
|
|
size="xl"
|
|
class="w-full"
|
|
placeholder="Cooperative corporation, LLC, partnership, etc." />
|
|
</UFormField>
|
|
|
|
<UFormField label="Registered in" class="form-group-inline">
|
|
<UInput
|
|
v-model="formData.registeredLocation"
|
|
placeholder="State/Province"
|
|
size="xl"
|
|
class="inline-field w-full"
|
|
@change="autoSave" />
|
|
</UFormField>
|
|
|
|
<div class="fiscal-year-group">
|
|
<UFormField label="Fiscal Year-End" class="form-group-inline">
|
|
<div class="flex gap-2 items-center">
|
|
<USelect
|
|
v-model="formData.fiscalYearEndMonth"
|
|
:items="monthOptions"
|
|
placeholder="Month"
|
|
size="xl"
|
|
class="w-60"
|
|
@change="autoSave" />
|
|
<USelect
|
|
v-model="formData.fiscalYearEndDay"
|
|
:items="dayOptions"
|
|
placeholder="Day"
|
|
size="xl"
|
|
class="w-40"
|
|
@change="autoSave" />
|
|
</div>
|
|
</UFormField>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="content-paragraph mb-3 leading-relaxed text-left">
|
|
This agreement works alongside but doesn't replace our legal
|
|
incorporation documents. Where they conflict, we follow the law
|
|
but work to align our legal structure with our values.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Export Options - Bottom -->
|
|
<ExportOptions
|
|
:export-data="exportData"
|
|
filename="membership-agreement"
|
|
title="Membership Agreement" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch } from "vue";
|
|
|
|
definePageMeta({
|
|
layout: false,
|
|
});
|
|
|
|
// Import PDF export composable
|
|
const { exportToPDF } = usePdfExportBasic();
|
|
|
|
const monthOptions = [
|
|
"January",
|
|
"February",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December",
|
|
];
|
|
|
|
const dayOptions = Array.from({ length: 31 }, (_, i) => ({
|
|
value: i + 1,
|
|
label: `${i + 1}${getOrdinalSuffix(i + 1)}`
|
|
}));
|
|
|
|
// Helper function to get ordinal suffix (1st, 2nd, 3rd, etc.)
|
|
function getOrdinalSuffix(num) {
|
|
if (num >= 11 && num <= 13) {
|
|
return 'th';
|
|
}
|
|
switch (num % 10) {
|
|
case 1: return 'st';
|
|
case 2: return 'nd';
|
|
case 3: return 'rd';
|
|
default: return 'th';
|
|
}
|
|
}
|
|
|
|
const payPolicyOptions = [
|
|
{ value: 'equal-pay', label: 'Equal Pay - All members receive equal compensation' },
|
|
{ value: 'hours-weighted', label: 'Hours-Weighted - Pay proportional to hours worked' },
|
|
{ value: 'needs-weighted', label: 'Needs-Weighted - Pay proportional to individual needs' }
|
|
];
|
|
|
|
const formData = ref({
|
|
cooperativeName: "",
|
|
dateEstablished: "",
|
|
purpose: "",
|
|
coreValues: "",
|
|
memberRequirements: "Shares our values and purpose\nContributes labour to the cooperative (by doing actual work, not just investing money)\nCommits to collective decision-making\nParticipates in governance responsibilities",
|
|
members: [{ name: "", email: "", joinDate: "", role: "" }],
|
|
trialPeriodMonths: 3,
|
|
buyInAmount: "",
|
|
noticeDays: 30,
|
|
surplusPayoutDays: 30,
|
|
buyInReturnDays: 90,
|
|
dayToDayLimit: 100,
|
|
regularDecisionMin: 100,
|
|
regularDecisionMax: 1000,
|
|
majorDebtThreshold: 5000,
|
|
meetingFrequency: "weekly",
|
|
emergencyNoticeHours: 24,
|
|
// Pay policy settings
|
|
payPolicy: "equal-pay",
|
|
baseRate: 25,
|
|
monthlyDraw: "",
|
|
hourlyRate: 25,
|
|
minGuaranteedPay: 1000,
|
|
paymentDay: 15,
|
|
surplusFrequency: "quarter",
|
|
targetHours: 40,
|
|
roleRotationMonths: 6,
|
|
rotatingRoles: "Financial coordinator (handles bookkeeping, not financial decisions)\nMeeting facilitator\nExternal communications\nOthers",
|
|
sharedResponsibilities: "Governance and decision-making\nStrategic planning\nMutual support and care",
|
|
reviewFrequency: "year",
|
|
assetDonationTarget: "",
|
|
legalStructure: "",
|
|
registeredLocation: "",
|
|
fiscalYearEndMonth: "December",
|
|
fiscalYearEndDay: 31,
|
|
});
|
|
|
|
// Load saved data immediately (before watchers)
|
|
const loadSavedData = () => {
|
|
const saved = localStorage.getItem("membership-agreement-data");
|
|
if (saved) {
|
|
try {
|
|
const parsedData = JSON.parse(saved);
|
|
formData.value = { ...formData.value, ...parsedData };
|
|
console.log("Loaded saved data:", parsedData);
|
|
} catch (error) {
|
|
console.error("Error loading saved data:", error);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Load data immediately
|
|
loadSavedData();
|
|
|
|
// Auto-save to localStorage (removed immediate to prevent overwriting)
|
|
watch(
|
|
formData,
|
|
(newData) => {
|
|
localStorage.setItem("membership-agreement-data", JSON.stringify(newData));
|
|
console.log("Auto-saved data");
|
|
},
|
|
{ deep: true }
|
|
);
|
|
|
|
// Also ensure data is loaded on mount as backup
|
|
onMounted(() => {
|
|
loadSavedData();
|
|
});
|
|
|
|
// Auto-save individual field changes immediately
|
|
const autoSave = () => {
|
|
localStorage.setItem(
|
|
"membership-agreement-data",
|
|
JSON.stringify(formData.value)
|
|
);
|
|
console.log("Manual auto-save triggered:", formData.value);
|
|
};
|
|
|
|
// Debounced auto-save for better performance
|
|
const debouncedAutoSave = debounce(autoSave, 300);
|
|
|
|
// Helper function for debouncing
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
// Member management functions
|
|
const addMember = () => {
|
|
formData.value.members.push({ name: "", email: "", joinDate: "", role: "" });
|
|
debouncedAutoSave();
|
|
};
|
|
|
|
const removeMember = (index) => {
|
|
if (formData.value.members.length > 1) {
|
|
formData.value.members.splice(index, 1);
|
|
debouncedAutoSave();
|
|
}
|
|
};
|
|
|
|
// Print function with proper form value handling
|
|
const handlePrint = () => {
|
|
// Create print-friendly version by replacing inputs with their values from formData
|
|
const inputs = document.querySelectorAll("input, textarea, select");
|
|
const printValues = [];
|
|
|
|
// Also create signature section replacement
|
|
const signatureSpace = document.querySelector(".signature-space");
|
|
let signatureReplacement = null;
|
|
|
|
if (signatureSpace) {
|
|
const signatureDiv = document.createElement("div");
|
|
signatureDiv.className = "print-signatures";
|
|
signatureDiv.innerHTML = `
|
|
<p style="font-weight: bold; margin-bottom: 12pt; color: #000;">Member Signatures</p>
|
|
<p style="margin-bottom: 16pt; color: #000;">By signing below, we agree to these terms and commit to working together as equals in this cooperative.</p>
|
|
`;
|
|
|
|
// Add signature lines for each member
|
|
const membersWithNames =
|
|
formData.value.members?.filter((m) => m.name) || [];
|
|
const numSignatures = Math.max(
|
|
2,
|
|
Math.min(8, membersWithNames.length || 4)
|
|
);
|
|
|
|
for (let i = 0; i < numSignatures; i++) {
|
|
const memberName = membersWithNames[i]?.name || "";
|
|
const sigDiv = document.createElement("div");
|
|
sigDiv.style.marginBottom = "24pt";
|
|
sigDiv.innerHTML = `
|
|
<p style="margin-bottom: 8pt; font-weight: bold; color: #000;">${
|
|
memberName || `Member ${i + 1}:`
|
|
}</p>
|
|
<div style="border-bottom: 1pt solid #000; width: 300pt; height: 16pt; margin-bottom: 4pt;"></div>
|
|
<p style="font-size: 9pt; color: #666;">Signature Date: ___________</p>
|
|
`;
|
|
signatureDiv.appendChild(sigDiv);
|
|
}
|
|
|
|
signatureReplacement = signatureDiv;
|
|
signatureSpace.style.display = "none";
|
|
signatureSpace.parentNode.insertBefore(signatureDiv, signatureSpace);
|
|
}
|
|
|
|
inputs.forEach((input, index) => {
|
|
// Get value from the actual formData reactive object instead of DOM
|
|
let value = "";
|
|
|
|
// Try to match the input to formData properties
|
|
if (input.closest('[label="Cooperative Name"]'))
|
|
value = formData.value.cooperativeName;
|
|
else if (input.closest('[label="Date Established"]'))
|
|
value = formData.value.dateEstablished;
|
|
else if (input.closest('[label="Our Purpose"]'))
|
|
value = formData.value.purpose;
|
|
else if (input.closest('[label="Our Core Values"]'))
|
|
value = formData.value.coreValues;
|
|
else if (input.closest('[label="Legal Structure"]'))
|
|
value = formData.value.legalStructure;
|
|
else if (input.closest('[label="Registered in"]'))
|
|
value = formData.value.registeredLocation;
|
|
else if (input.closest('[label="Fiscal Year-End"]')) {
|
|
if (input.tagName === "SELECT" || input.type === "select-one") {
|
|
value = formData.value.fiscalYearEndMonth;
|
|
} else {
|
|
value = formData.value.fiscalYearEndDay;
|
|
}
|
|
}
|
|
// Handle member fields
|
|
else if (input.closest(".border-neutral-200")) {
|
|
const memberCard = input.closest(".border-neutral-200");
|
|
const memberIndex = Array.from(memberCard.parentNode.children).indexOf(
|
|
memberCard
|
|
);
|
|
const member = formData.value.members?.[memberIndex];
|
|
if (member) {
|
|
if (input.closest('[label="Full Name"]')) value = member.name;
|
|
else if (input.closest('[label="Email"]')) value = member.email;
|
|
else if (input.closest('[label="Join Date"]')) value = member.joinDate;
|
|
else if (input.closest('[label="Current Role (Optional)"]'))
|
|
value = member.role;
|
|
}
|
|
}
|
|
// Fallback to input.value
|
|
else {
|
|
value = input.value?.trim() || "";
|
|
}
|
|
|
|
// Create a span element to replace the input for print
|
|
const printSpan = document.createElement("span");
|
|
printSpan.className = "print-value";
|
|
|
|
if (value) {
|
|
// Has a value - show it in bold
|
|
printSpan.textContent = value;
|
|
printSpan.style.fontWeight = "bold";
|
|
} else {
|
|
// Empty field - show as blank line
|
|
printSpan.innerHTML = " ";
|
|
printSpan.setAttribute("data-empty", "true");
|
|
}
|
|
|
|
printSpan.setAttribute("data-original-index", index);
|
|
|
|
// Store original element info for restoration
|
|
printValues.push({
|
|
element: input,
|
|
parent: input.parentNode,
|
|
nextSibling: input.nextSibling,
|
|
replacement: printSpan,
|
|
});
|
|
|
|
// Replace input with span for print
|
|
input.style.display = "none";
|
|
input.parentNode.insertBefore(printSpan, input);
|
|
});
|
|
|
|
// Trigger print
|
|
setTimeout(() => {
|
|
window.print();
|
|
|
|
// Restore original inputs after print dialog
|
|
setTimeout(() => {
|
|
printValues.forEach((item) => {
|
|
item.element.style.display = "";
|
|
if (item.replacement.parentNode) {
|
|
item.replacement.parentNode.removeChild(item.replacement);
|
|
}
|
|
});
|
|
|
|
// Restore signature section
|
|
if (signatureReplacement && signatureSpace) {
|
|
signatureSpace.style.display = "";
|
|
if (signatureReplacement.parentNode) {
|
|
signatureReplacement.parentNode.removeChild(signatureReplacement);
|
|
}
|
|
}
|
|
}, 100);
|
|
}, 100);
|
|
};
|
|
|
|
// Export data for the ExportOptions component
|
|
const exportData = computed(() => ({
|
|
// Pass the complete formData object - this is what the export functions use
|
|
formData: formData.value,
|
|
// Also provide direct access to key fields for backward compatibility
|
|
cooperativeName: formData.value.cooperativeName || "Worker Cooperative",
|
|
section: "membership-agreement",
|
|
exportedAt: new Date().toISOString(),
|
|
}));
|
|
</script>
|
|
|
|
<style scoped>
|
|
@reference "tailwindcss";
|
|
|
|
/* Template-specific styles not in main.css */
|
|
|
|
.template-content {
|
|
max-width: none;
|
|
line-height: 1.6;
|
|
color: #1f2937;
|
|
}
|
|
|
|
/* Fiscal year group styling */
|
|
.fiscal-year-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.fiscal-year-group .flex {
|
|
align-items: center;
|
|
}
|
|
|
|
/* Signature space styling */
|
|
.signature-space {
|
|
margin-top: 2rem;
|
|
padding: 2rem 1rem;
|
|
border: 1px dashed #d1d5db;
|
|
border-radius: 0.375rem;
|
|
background: #f9fafb;
|
|
min-height: 4rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* Review dates styling */
|
|
.review-dates {
|
|
background: #f9fafb;
|
|
padding: 1rem;
|
|
border-radius: 0.375rem;
|
|
border-left: 3px solid #3b82f6;
|
|
}
|
|
|
|
/* Font families */
|
|
.template-content.font-source-serif,
|
|
.template-content.font-source-serif * {
|
|
font-family: "Source Serif 4", "Source Serif Pro", Georgia, serif !important;
|
|
}
|
|
|
|
.template-content.font-ubuntu,
|
|
.template-content.font-ubuntu * {
|
|
font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
sans-serif !important;
|
|
}
|
|
|
|
.template-content.font-inter,
|
|
.template-content.font-inter * {
|
|
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
sans-serif !important;
|
|
}
|
|
</style>
|