Статьи

Создание вашего стартапа: завершение группового планирования

Конечный продукт
Что вы будете создавать

Это руководство является частью серии « Создай свой стартап с помощью PHP» на Envato Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения.

Добро пожаловать! Это продолжение серии « Создание стартапа: встречи с несколькими участниками» . Сегодня я закончу работу, которую мы начали в этом эпизоде: планирование нескольких встреч участников.

Планирование встреч с несколькими участниками всегда было главной целью для Планировщика собраний, который запускался только с расписанием 1: 1. Встречи с несколькими участниками являются наиболее сложными для людей, чтобы попытаться составить расписание друг с другом, и поэтому являются одной из наиболее ценных функций, предоставляемых службой Планировщик собраний.

В сегодняшнем уроке я расскажу о рассмотрении всех областей сайта, затронутых несколькими собраниями участников, обработке и умном отображении списков получателей различного статуса, правильном управлении уведомлениями и фильтрации уведомлений для групп и, наконец, обновлении недавно запущенного запроса. особенность изменений встречи .

Пожалуйста, запланируйте свою собственную групповую встречу сегодня ! Пригласите нескольких друзей встретиться с вами для kombucha или kava. Поделитесь своими мыслями и отзывами об опыте каждого в комментариях ниже. Я участвую в обсуждениях, но вы также можете связаться со мной @reifman в Twitter. Я всегда открыт для новых идей для планировщика собраний, а также предложений для будущих серий.

Напоминаем, что весь код для Meeting Planner предоставляется с открытым исходным кодом и написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с моей параллельной серией Программирование с Yii2 .

Как вы можете себе представить, преобразование Планировщика собраний из собраний 1: 1 в групповые собрания коснулось почти всего кода. Мне пришлось продумать все области, пересмотреть код и внести небольшие или умеренные изменения во многих местах. В других областях я разработал несколько участников, и изменения не были нужны или были незначительными.

Например, групповое планирование коснулось:

В основном, когда я ранее просто отправлял участнику [0], первому и единственному участнику, мне теперь нужно было обработать массив участников. И при этом мне нужно было проверить:

  • Является ли этот участник организатором?
  • Этот человек был удален или отклонен самостоятельно?
  • В будущем этот человек отказался от уведомлений об этой встрече?

Имея больше ресурсов, я мог бы справиться с этим более полно с помощью автоматического тестирования. Тем не менее, работая в одиночку с поставкой в ​​качестве цели, я полностью проверил все вручную.

Я использовал электронную почту домена catchall, чтобы пригласить n1, n2, n3, n4 и n5 @ mytestdomain.com на собрания моей группы образцов. К счастью, приглашения Планировщика собраний позволяют быстро войти в систему с любой учетной записью, щелкнув каждое приглашение на собрание; это помогло моему тестированию.

Важно было просмотреть почти весь код планирования собраний.

Но вернемся к более конкретным задачам кодирования второй половины функции группового планирования.

Некоторое время назад я MiscHelpers метод MiscHelpers для грамматического отображения списков на английском языке с «и» перед фамилией, как показано в указателе собрания ниже:

Планирование группы Startup Series - индекс встречи

Однако я хотел упростить отображение даты, времени и места доступности. Например, вместо того, чтобы перечислять пять имен людей, которые приняли встречу в Herkimer Coffee, я обновил MiscHelpers::listNames чтобы сказать «все остальные»:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static function listNames($items,$everyoneElse=false,$total_count=0,$anyoneElse=false) {
  $temp =»;
  $x=1;
  $cnt = count($items);
  if ($everyoneElse && $cnt >= ($total_count-1)) {
    if (!$anyoneElse) {
        $temp = Yii::t(‘frontend’,’everyone else’);
    } else {
      $temp = Yii::t(‘frontend’,’anyone else’);
    }
  } else {
    foreach ($items as $i) {
        $temp.= MiscHelpers::getDisplayName($i);
        if ($x == ($cnt-1)) {
          $temp.=’ and ‘;
        } else if ($x < ($cnt-1)) {
          $temp.=’, ‘;
        }
        $x+=1;
    }
  }
  return $temp;
}

Вы можете увидеть это в действии ниже:

Планирование групп Startup Series - списки доступности по времени и месту

Но вместо того, чтобы сказать «нет ответа от всех остальных», правильнее будет сказать «нет ответа от кого-либо еще», что делает код.

Ниже вы можете увидеть, что MeetingPlace::getWhereStatus() подготавливает эти строки для каждого места на панели мест собраний:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public static function getWhereStatus($meeting,$viewer_id) {
  // get an array of textual status of meeting places for $viewer_id
  // Acceptable / Rejected / No response:
  $whereStatus[‘style’] = [];
  $whereStatus[‘text’] = [];
  foreach ($meeting->meetingPlaces as $mp) {
    // build status for each place
    $acceptableChoice=[];
    $rejectedChoice=[];
    $unknownChoice=[];
    // to do — add meeting_id to MeetingPlaceChoice for sortable queries
    foreach ($mp->meetingPlaceChoices as $mpc) {
      if ($mpc->user_id == $viewer_id) continue;
      switch ($mpc->status) {
        case MeetingPlaceChoice::STATUS_UNKNOWN:
          $unknownChoice[]=$mpc->user_id;
        break;
        case MeetingPlaceChoice::STATUS_YES:
          $acceptableChoice[]=$mpc->user_id;
        break;
        case MeetingPlaceChoice::STATUS_NO:
          $rejectedChoice[]=$mpc->user_id;
        break;
      }
    }
    // to do — integrate current setting for this user in style setting
    $temp =»;
    // count those still in attendance
    $cntP = Participant::find()
      ->where([‘meeting_id’=>$meeting->id])
      ->andWhere([‘status’=>Participant::STATUS_DEFAULT])
      ->count()+1;
    if (count($acceptableChoice)>0) {
      $temp.=’Acceptable to ‘.MiscHelpers::listNames($acceptableChoice,true,$cntP).’.
      $whereStatus[‘style’][$mp->place_id]=’success’;
    }
    if (count($rejectedChoice)>0) {
      $temp.=’Rejected by ‘.MiscHelpers::listNames($rejectedChoice,true,$cntP).’.
      $whereStatus[‘style’][$mp->place_id]=’danger’;
    }
    if (count($unknownChoice)>0) {
      $temp.=’No response from ‘.MiscHelpers::listNames($unknownChoice,true,$cntP,true).’.’;
      $whereStatus[‘style’][$mp->place_id]=’warning’;
    }
    $whereStatus[‘text’][$mp->place_id]=$temp;
  }
  return $whereStatus;
}

У каждого пользователя есть строка MeetingPlaceChoice связанная с MeetingPlace которой записывается, является ли место приемлемым, неприемлемым или еще не отвеченным. MeetingTimeChoice также существует аналогичным образом. Эта информация передается в listNames() .

Группам также требовалось больше сложности с отклонением приглашения на встречу. Ранее, если один участник отказался, собрание было фактически отменено. Теперь можно отказаться, оставив еще трех.

Поэтому, если участник получает приглашение на встречу, он может отклонить его. Но если встреча уже была подтверждена и завершена, то они, по сути, выводятся, как вы можете видеть ниже:

Планирование группы Startup Series - кнопка «Снять» в представлении собрания

Примечание. На изображении выше Сара Смитерс видит кнопку « Снять» ; Роберт МакСмит был удален другим организатором, Джеффом или Алексом .

Однако, если это организатор (владелец собрания или назначенный участник-организатор), они могут просто отменить собрание. Ниже от _command_bar_confirmed.php. Он определяет, какие кнопки представить:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
if (!$isPast) {
 if ($model->isOrganizer()) {
   echo Html::a(‘<i class=»glyphicon glyphicon-remove-circle»></i>&nbsp;’.Yii::t(‘frontend’, ‘Cancel’), [‘cancel’, ‘id’ => $model->id],
  [‘class’ => ‘btn btn-primary btn-danger’,
  ‘title’=>Yii::t(‘frontend’,’Cancel’),
  ‘data-confirm’ => Yii::t(‘frontend’, ‘Are you sure you want to cancel this meeting?’)
  ]) ;
 }
 else {
   if ($model->getParticipantStatus(Yii::$app->user->getId())==Participant::STATUS_DEFAULT) {
     echo Html::a(‘<i class=»glyphicon glyphicon-remove-circle»></i>&nbsp;’.Yii::t(‘frontend’, ‘Withdraw’), [‘decline’, ‘id’ => $model->id],
    [‘class’ => ‘btn btn-primary btn-danger’,
    ‘title’=>Yii::t(‘frontend’,’Withdraw from the meeting’),
    ‘data-confirm’ => Yii::t(‘frontend’, ‘Are you sure you want to decline attendance to this meeting?’)
    ]) ;
  } else {
    // to do — offer rejoin meeting option
  }

Расстановка приоритетов является ключевым элементом построения стартапа. Поэтому, хотя я хотел предложить пользователю, который отошел, присоединиться к собранию, я решил добавить его в список задач Asana на потом. Пользователю, который присоединяется, потребуются обновленные уведомления и, возможно, некоторые обновления их структур данных планирования.

В то время как при собраниях 1: 1 каждое изменение нужно было отправлять другой стороне, это не обязательно имеет смысл для собраний из 12 человек — или так? По-разному.

Изначально я создал общие рекомендации. Если участник обновлял свои предпочтения для определенной даты или времени, об этом нужно было сообщить только владельцу и другим организаторам.

Я создал массив $groupSkip в MeetingLog который определял события, которые не следует отправлять другим участникам:

1
2
3
4
5
6
7
8
public static $groupSkip=[
  MeetingLog::ACTION_ACCEPT_ALL_PLACES,
  MeetingLog::ACTION_ACCEPT_PLACE,
  MeetingLog::ACTION_REJECT_PLACE,
  MeetingLog::ACTION_ACCEPT_ALL_TIMES,
  MeetingLog::ACTION_ACCEPT_TIME,
  MeetingLog::ACTION_REJECT_TIME
];

В MeetingLog::getHistory мы пропускаем уведомление участника об этих событиях, но всегда уведомляем организаторов:

1
2
3
4
5
6
7
8
if (
// skip over availability response events in multi participant meetings
  ($isGroup && !$isOrganizer && in_array($e->action,MeetingLog::$groupSkip))
  ) {
    $num_events-=1;
    continue;
  }

В одном необычном примере код был на самом деле упрощен для нескольких участников: Meeting::findFresh() , который ищет обновления изменений собрания, чтобы поделиться ими по электронной почте.

Ранее нам нужно было определить, кто из двух пользователей выполнил действия и стоит ли уведомлять либо. Теперь мы просто уведомляем владельца, а затем уведомляем участников:

01
02
03
04
05
06
07
08
09
10
11
12
if ((time()-$m->logged_at) > MeetingLog::TIMELAPSE && $m->status>=Meeting::STATUS_SENT) { //
 // get logged items which occured after last cleared_at
  $m->notify($m->id,$m->owner_id);
   // notify the participants
   foreach ($m->participants as $p) {
     // don’t update removed and declined participants
     if ($p->status!=Participant::STATUS_DEFAULT) {
        continue;
      }
      //echo ‘Notify P-id: ‘.$p->participant_id.'<br />’;
      $m->notify($m->id,$p->participant_id);
   }

Любая фильтрация выполняется глубже в текстуализации журнала событий.

Я также создал новое уведомление для оповещения организаторов, когда все согласны хотя бы с одним конкретным местом и временем, MeetingLog::ACTION_SEND_EVERYONE_AVAILABLE :

1
2
3
4
5
// check if meeting has place and time for everyone now
if (count($m->participants)>1 && !MeetingLog::hasEventOccurred($m->id,MeetingLog::ACTION_SEND_EVERYONE_AVAILABLE) && Meeting::isEveryoneAvailable($m->id)) {
  Meeting::notifyOrganizers($m->id,MeetingLog::ACTION_SEND_EVERYONE_AVAILABLE);
  MeetingLog::add($m->id,MeetingLog::ACTION_SEND_EVERYONE_AVAILABLE,0);
}

Это уведомляет организаторов, когда собрание готово завершить / подтвердить.

Вот код, который просматривает все места встреч и время встреч, чтобы узнать, согласны ли все хотя бы на одно место:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public static function isEveryoneAvailable($meeting_id) {
  // check that one place works for everyone attending
  $m = Meeting::findOne($meeting_id);
  $cntAll = $m->countAttendingParticipants(true);
  // count organizer + attending participants
  $mpExists=false;
  $mtExists=true;
  $mps = \frontend\models\MeetingPlace::find()->where([‘meeting_id’=>$meeting_id])->all();
  foreach ($mps as $mp) {
    $cnt=0;
    foreach ($mp->meetingPlaceChoices as $mpc) {
      if ($m->getParticipantStatus($mpc->user_id)!=Participant::STATUS_DEFAULT) {
        // skip withdrawn, declined, removed participants
        continue;
      }
      if ($mpc->status == \frontend\models\MeetingPlaceChoice::STATUS_YES) {
        $cnt+=1;
      }
    }
    if ($cnt >=$cntAll) {
      $mpExists = true;
    }
  }
  $mts = \frontend\models\MeetingTime::find()->where([‘meeting_id’=>$meeting_id])->all();
  foreach ($mts as $mt) {
    $cnt=0;
    foreach ($mt->meetingTimeChoices as $mtc) {
      if ($m->getParticipantStatus($mtc->user_id)!=Participant::STATUS_DEFAULT) {
        // skip withdrawn, declined, removed participants
        continue;
      }
      if ($mtc->status == \frontend\models\MeetingTimeChoice::STATUS_YES) {
        $cnt+=1;
      }
    }
    if ($cnt >=$cntAll) {
      $mtExists = true;
    }
  }
  // at least one time and one place works for everyone attending
  if ($mpExists && $mtExists) {
    return true;
  } else {
    return false;
  }
}

Точно так же я построил функцию для уведомления организатора о том, что ни одно из значений времени и места не является приемлемым для кого-либо, Meeting::isSomeoneAvailable() :

1
2
3
4
5
6
7
8
if ($model->status <= Meeting::STATUS_SENT) {
 if ($model->isOrganizer()
   && ($model->status == Meeting::STATUS_SENT)
   && !$model->isSomeoneAvailable()) {
   Yii::$app->getSession()->setFlash(‘danger’,
   Yii::t(‘frontend’,
   ‘None of the participants are available for the meeting\’s current options.’));
 }

Это указывает на то, что они должны предложить дополнительные даты и / или места.

Все, что касается напоминаний о собраниях, хорошо работало для нескольких участников, но мне нужно было отключить напоминания, если участник отклонился от участия в совещании или был удален:

1
2
3
4
5
6
$cnt =1;
foreach ($mtg->participants as $p) {
if ($p->status ==Participant::STATUS_DEFAULT) {
 $attendees[$cnt]=$p->participant_id;
 $cnt+=1;
 }

STATUS_DEFAULT указывает посетителя, который также должен быть добавлен в массив пользователей для отправки напоминаний по электронной почте.

Я также проверил работу по созданию файлов календаря для приглашений, чтобы убедиться, что все участники включены. В Meeting::prepareDownloadIcs() я собрал массив посетителей с владельцем и участниками, которые активно посещают:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
$attendees = array();
foreach ($m->participants as $p) {
  if ($p->status ==Participant::STATUS_DEFAULT) {
    $auth_key=\common\models\User::find()->where([‘id’=>$p->participant_id])->one()->auth_key;
    $attendees[$cnt]=[‘user_id’=>$p->participant_id,’auth_key’=>$auth_key,
    ’email’=>$p->participant->email,
    ‘username’=>$p->participant->username];
    $cnt+=1;
    // reciprocate friendship to organizer
    \frontend\models\Friend::add($p->participant_id,$p->invited_by);
    // to do — reciprocate friendship in multi participant meetings
  }
}
$auth_key=\common\models\User::find()->where([‘id’=>$m->owner_id])->one()->auth_key;
$attendees[$cnt]=[‘user_id’=>$m->owner_id,
  ‘auth_key’=>$auth_key,
  ’email’=>$m->owner->email,
  ‘username’=>$m->owner->username];
foreach ($attendees as $cnt=>$a) {
  if ($a[‘user_id’]==$actor_id) {
    $icsPath = Meeting::buildCalendar($m->id,$chosenPlace,$chosenTime,$a,$attendees);

За это время я также узнал, как указать, что файл календаря отмененной встречи должен инициировать удаление события из чьего-либо календаря. Стандарт ИКС является мощным, хотя не легко выучить.

Как я писал недавно, функция « Запрашивать изменения собрания» потребовала много работы и нового UX.

Планирование группы Startup Series - запросить изменение вашей встречи

Для нескольких встреч участников социальная инженерия должна была измениться снова. Например, организаторы могут принимать или отклонять запросы и изменять расписание встреч. Тем не менее, участники могут выражать только «нравится», «не нравится» или «не заботиться» о запросах на изменение. И организаторы должны видеть ответы всех участников, чтобы помочь им в принятии решений.

Вот что видит участник после отправки запроса на изменение:

Планирование группы Startup Series - подтверждение вашего запроса

Новые запросы на изменение должны быть отправлены всем участникам. Это прозрачно обрабатывается уведомлениями журнала активности. Когда запрос сделан, это событие создается в RequestController::actionCreate() submit:

1
MeetingLog::add($model->meeting_id,MeetingLog::ACTION_REQUEST_CREATE,Yii::$app->user->getId(),$model->id);

Вот как выглядит запрошенное уведомление об изменении для других участников:

Планирование группы Startup Series - уведомление по электронной почте о запрошенных изменениях для участников

Всех просят ответить. Нажатие « Ответить на запрос» переходит прямо к запросу. Или вы можете найти его в списке запросов по ссылке на оповещение о вспышке на встрече, показанной выше.

Планирование группы Startup Series - список запросов

Вот форма, которую участники видят, когда они отвечают на запрос:

Планирование групп Startup Series - представление участниками ответа на запрос

Если уже есть ответы других участников, они увидят их:

Групповое планирование серии запуска - Групповое планирование серии запуска - представление участниками ответа на запрос с другими ответами

Вот верхняя часть этой формы в /frontend/views/request-response/_form.php:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<p><em>
<?= $subject ?>
</em>
</p>
 
<?= GridView::widget([
    ‘dataProvider’ => $responseProvider,
    ‘columns’ => [
      [
      ‘label’=>’Responses from Other Participants’,
        ‘attribute’ => ‘responder_id’,
        ‘format’ => ‘raw’,
        ‘value’ => function ($model) {
                $note=»;
                if (!empty($model->note)) {
                  $note = ‘ said, «‘.$model->note.’»‘;
                }
                return ‘<div>’.MiscHelpers::getDisplayName($model->responder_id).’
            },
    ],
  ],
]);
<div class=»request-response-form»>
    <?php $form = ActiveForm::begin();
      <?= BaseHtml::activeHiddenInput($model, ‘responder_id’);
        <?= BaseHtml::activeHiddenInput($model, ‘request_id’);
    <?= $form->field($model, ‘note’)->label(Yii::t(‘frontend’,’Include a note’))->textarea([‘rows’ => 6])->hint(Yii::t(‘frontend’,’optional’)) ?>

В Gridview перечислены существующие ответы, например, «Нравится», «Не нравится», «Нейтрально» и любые личные заметки.

Затем, вот код для нижней половины формы, который будет отображать « нравится», «не нравится», «все равно» для участников, но принимать и вносить изменения или отклонять запрос для владельцев.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
  if (!$isOwner && $isOrganizer) {
?>
  <p><em><?= Yii::t(‘frontend’,’Since you are an organizer, you can accept the request and make the changes or reject it.’);?></em></p>
<?php
  }
?>
<?php
  if ($isOrganizer) {
?>
<div class=»form-group»>
  <?= Html::submitButton(Yii::t(‘frontend’, ‘Accept and Make Changes’), [‘class’ => ‘btn btn-success’,’name’=>’accept’,]) ?>
  <?= Html::submitButton(Yii::t(‘frontend’, ‘Decline Request’),[‘class’ => ‘btn btn-danger’,’name’=>’reject’,
      ‘data’ => [
          ‘confirm’ => Yii::t(‘frontend’, ‘Are you sure you want to decline this request?’),
          ‘method’ => ‘post’,
      ],]) ?>
</div>
<?php
  }
?>
<?php
  if (!$isOwner && $isOrganizer) {
?>
  <p><em><?= Yii::t(‘frontend’,’Or, you can just express your opinion and defer to other organizers.’);?></em></p>
<?php
  }
?>
<?php
  if (!$isOwner) {
?>
<?php
  if (!$isOrganizer) {
?>
<p><em><?= Yii::t(‘frontend’,’Please share your opinion of this request for the organizers to consider.’);?></em></p>
<?php
 }
 ?>
<div class=»form-group»>
  <?= Html::submitButton(Yii::t(‘frontend’, ‘Like’), [‘class’ => ‘btn btn-success’,’name’=>’like’,]) ?>
  <?= Html::submitButton(Yii::t(‘frontend’, ‘Don\’t Care’), [‘class’ => ‘btn btn-info’,’name’=>’neutral’,]) ?>
  <?= Html::submitButton(Yii::t(‘frontend’, ‘Dislike’),[‘class’ => ‘btn btn-danger’,’name’=>’dislike’,]) ?>
 
</div>
<?php
  }
?>
    <?php ActiveForm::end();
Планирование групп Startup Series - представление организаторами ответа на запрос

Участникам, которые являются помазанными организаторами, показаны оба набора кнопок, и они могут либо высказать свое мнение, либо внести или отклонить изменение.

Вот электронное уведомление о том, что изменение было принято:

Планирование группы Startup Series - уведомление по электронной почте о том, что изменение было принято

Конечно, обновленное приглашение и файлы календаря будут разосланы всем, если будут внесены изменения.

Надеюсь, вам понравились эти два эпизода (сегодняшний и « Построение стартапа: встречи с несколькими участниками» ). В режиме запуска с огромной новой функцией всегда есть целенаправленное, оптимизированное усилие по запуску, которое оставляет множество незакрепленных концов невыполненными и неполированные дефекты.

Вот несколько примеров этого:

  • Присоединение к собраниям, от которых вы отказались или от которых отказались.
  • Улучшено представление списка участников в приглашениях.
  • Варианты сохранения списка участников и / или их индивидуальных статусов закрытыми от других участников.
  • Улучшена обработка и представление контактной информации группы и сведений о виртуальной конференции, например, линия конференции и код участия.
  • Безопасный URL для обмена приглашениями на собрания. Это позволило бы организаторам поделиться URL-адресом на Facebook или по электронной почте, чтобы пригласить новых участников.

Несмотря на эти недостатки, я очень усердно работал, чтобы достичь такого уровня функций и удобства использования для Meeting Planner . Я очень взволнован его прогрессом и слышу хорошие отзывы об этом от друзей и коллег.

Сегодня я перехожу к этим двум урокам и провожу несколько дней в лесу без выходных. Отдых важен. Общение с природой помогает напомнить вам о том, что важно в жизни — стартапы не всегда, на самом деле. Они могут быть творческими занятиями, важными для нашего дохода и карьеры, они могут в некоторых случаях помочь людям жить более эффективно и продуктивно, но они часто далеки от земли, от дружеских отношений и от помощи другим менее удачливым. Это все, о чем я думаю каждый день и буду делать снова, пока меня нет.

Я постоянно спрашиваю себя, делаю ли я все, что хочу делать со своим временем, особенно в свете операции на мозге .

Я также приму к сведению тот факт, что я горжусь Meeting Planner, особенно работой на сегодняшний день и ее растущей полезностью. В целом, бета-версия сервиса близка к завершению.

Многочисленные собрания участников были самым сложным и сложным оставшимся рабочим вопросом. Заглядывая в будущее, функции и задачи являются более умеренными, небольшими и более легко управляемыми. Я взволнован о его будущих перспективах!

Если вы еще этого не сделали, запланируйте свою первую встречу с Meeting Planner прямо сейчас!

Учебное пособие по краудфандингу также находится в разработке, поэтому, пожалуйста, следуйте нашей странице WeFunder Meeting Planner .

Вы также можете обратиться ко мне @reifman . Я всегда открыт для новых идей и тематических предложений для будущих уроков.

Следите за всеми этими и другими учебниками, ознакомившись с серией « Построение стартапа на PHP» .