diff --git a/src/main/java/com/techlooper/config/CoreConfiguration.java b/src/main/java/com/techlooper/config/CoreConfiguration.java index a3a1acd29..902961791 100644 --- a/src/main/java/com/techlooper/config/CoreConfiguration.java +++ b/src/main/java/com/techlooper/config/CoreConfiguration.java @@ -190,7 +190,10 @@ protected void configure() { mapping(ChallengeEntity.class, ChallengeDto.class) .fields("startDateTime", "startDate") .fields("submissionDateTime", "submissionDate") - .fields("registrationDateTime", "registrationDate"); + .fields("registrationDateTime", "registrationDate") + .fields("ideaSubmissionDateTime", "ideaSubmissionDate") + .fields("uxSubmissionDateTime", "uxSubmissionDate") + .fields("prototypeSubmissionDateTime", "prototypeSubmissionDate"); } }); @@ -504,6 +507,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws public MimeMessage fromTechlooperMailMessage(JavaMailSender mailSender) throws MessagingException, UnsupportedEncodingException { MimeMessage mailMessage = mailSender.createMimeMessage(); mailMessage.setFrom(new InternetAddress(mailTechlooperForm, "TechLooper", "UTF-8")); + mailMessage.setReplyTo(InternetAddress.parse(mailTechlooperReplyTo)); return mailMessage; } @@ -513,4 +517,16 @@ private ClientHttpRequestFactory clientHttpRequestFactory() { factory.setConnectTimeout(5000); return factory; } + + @Bean + public Template challengeEmployerMailTemplateEn(freemarker.template.Configuration freemakerConfig) throws IOException { + Template template = freemakerConfig.getTemplate("challengeEmployerEmail.en.ftl"); + return template; + } + + @Bean + public Template challengeEmployerMailTemplateVi(freemarker.template.Configuration freemakerConfig) throws IOException { + Template template = freemakerConfig.getTemplate("challengeEmployerEmail.vi.ftl"); + return template; + } } \ No newline at end of file diff --git a/src/main/java/com/techlooper/controller/ChallengeController.java b/src/main/java/com/techlooper/controller/ChallengeController.java index 30ff4be08..e1387de44 100644 --- a/src/main/java/com/techlooper/controller/ChallengeController.java +++ b/src/main/java/com/techlooper/controller/ChallengeController.java @@ -2,9 +2,11 @@ import com.techlooper.entity.ChallengeEntity; import com.techlooper.entity.ChallengeRegistrantDto; +import com.techlooper.entity.ChallengeRegistrantEntity; import com.techlooper.entity.vnw.VnwCompany; import com.techlooper.entity.vnw.VnwUser; import com.techlooper.model.*; +import com.techlooper.repository.elasticsearch.ChallengeRegistrantRepository; import com.techlooper.service.ChallengeService; import com.techlooper.service.EmployerService; import com.techlooper.service.LeadAPIService; @@ -34,6 +36,9 @@ public class ChallengeController { @Resource private LeadAPIService leadAPIService; + @Resource + private ChallengeRegistrantRepository challengeRegistrantRepository; + @PreAuthorize("hasAuthority('EMPLOYER')") @RequestMapping(value = "/challenge/publish", method = RequestMethod.POST) public ChallengeResponse publishChallenge(@RequestBody ChallengeDto challengeDto, HttpServletRequest servletRequest) throws Exception { @@ -125,5 +130,10 @@ public ChallengeRegistrantDto saveRegistrant(@RequestBody ChallengeRegistrantDto return challengeService.saveRegistrant(request.getRemoteUser(), dto); } - + @PreAuthorize("hasAuthority('EMPLOYER')") + @RequestMapping(value = "challengeRegistrant/fullName/{registrantId}", method = RequestMethod.GET) + public String getChallengeRegistrant(@PathVariable Long registrantId) { + ChallengeRegistrantEntity registrantEntity = challengeRegistrantRepository.findOne(registrantId); + return registrantEntity.getRegistrantFirstName() + " " + registrantEntity.getRegistrantLastName(); + } } diff --git a/src/main/java/com/techlooper/controller/UserController.java b/src/main/java/com/techlooper/controller/UserController.java index 629dc5baf..3ec542550 100644 --- a/src/main/java/com/techlooper/controller/UserController.java +++ b/src/main/java/com/techlooper/controller/UserController.java @@ -3,6 +3,7 @@ import com.techlooper.dto.DashBoardInfo; import com.techlooper.dto.JoinBySocialDto; import com.techlooper.dto.WebinarInfoDto; +import com.techlooper.entity.ChallengeRegistrantEntity; import com.techlooper.entity.GetPromotedEntity; import com.techlooper.entity.PriceJobEntity; import com.techlooper.entity.SalaryReviewEntity; @@ -33,6 +34,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; @@ -373,4 +375,36 @@ public WebinarInfoDto findWebinarById(@PathVariable Long id) { public WebinarInfoDto joinWebinar(@RequestBody JoinBySocialDto joinBySocialDto) throws Exception { return webinarService.joinWebinar(joinBySocialDto); } + + @PreAuthorize("hasAuthority('EMPLOYER')") + @RequestMapping(value = "user/challengeRegistrantNames/{challengeId}/{now}", method = RequestMethod.GET) + public List getDailyChallengeRegistrantNames(HttpServletRequest request, HttpServletResponse response, + @PathVariable Long challengeId, @PathVariable Long now) { + if (challengeService.isOwnerOfChallenge(request.getRemoteUser(), challengeId)) { + List registrants = challengeService.findChallengeRegistrantWithinPeriod(challengeId, now, TimePeriodEnum.TWENTY_FOUR_HOURS); + return registrants.stream() + .map(registrant -> registrant.getRegistrantFirstName() + " " + registrant.getRegistrantLastName()) + .collect(Collectors.toList()); + } + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return null; + } + + @PreAuthorize("hasAuthority('EMPLOYER')") + @RequestMapping(value = "user/challenge/sendMailToDaily/{challengeId}/{now}", method = RequestMethod.POST) + public void sendEmailToDailyChallengeRegistrants(HttpServletRequest request, HttpServletResponse response, + @PathVariable Long challengeId, @PathVariable Long now, @RequestBody EmailContent emailContent) { + if (!challengeService.sendEmailToDailyChallengeRegistrants(request.getRemoteUser(), challengeId, now, emailContent)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } + + @PreAuthorize("hasAuthority('EMPLOYER')") + @RequestMapping(value = "user/challenge/feedback/{challengeId}/{registrantId}", method = RequestMethod.POST) + public void sendFeedbackToRegistrant(HttpServletRequest request, HttpServletResponse response, + @PathVariable Long challengeId, @PathVariable Long registrantId, @RequestBody EmailContent emailContent) { + if (!challengeService.sendEmailToRegistrant(request.getRemoteUser(), challengeId, registrantId, emailContent)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } } diff --git a/src/main/java/com/techlooper/cron/ChallengeTimelineNotifier.java b/src/main/java/com/techlooper/cron/ChallengeTimelineNotifier.java index fd2cfd980..786464401 100644 --- a/src/main/java/com/techlooper/cron/ChallengeTimelineNotifier.java +++ b/src/main/java/com/techlooper/cron/ChallengeTimelineNotifier.java @@ -37,6 +37,7 @@ public void notifyRegistrantAboutChallengeTimeline() throws Exception { if (enableJobAlert) { List challengePhases = Arrays.asList(ChallengePhaseEnum.REGISTRATION, ChallengePhaseEnum.IN_PROGRESS); + int count = 0; for (ChallengePhaseEnum challengePhase : challengePhases) { List challengeEntities = challengeService.listChallengesByPhase(challengePhase); @@ -50,6 +51,7 @@ public void notifyRegistrantAboutChallengeTimeline() throws Exception { if (StringUtils.isNotEmpty(challengeRegistrantEntity.getRegistrantEmail())) { challengeService.sendEmailNotifyRegistrantAboutChallengeTimeline( challengeEntity, challengeRegistrantEntity, challengePhase); + count++; } } catch (Exception ex) { LOGGER.error(ex.getMessage(), ex); @@ -57,6 +59,7 @@ public void notifyRegistrantAboutChallengeTimeline() throws Exception { } } } + LOGGER.info("There are " + count + " emails has been sent to notify challenge timeline"); } } diff --git a/src/main/java/com/techlooper/cron/DailyChallengeSummaryEmailSender.java b/src/main/java/com/techlooper/cron/DailyChallengeSummaryEmailSender.java index 2bdb6530d..18af951bd 100644 --- a/src/main/java/com/techlooper/cron/DailyChallengeSummaryEmailSender.java +++ b/src/main/java/com/techlooper/cron/DailyChallengeSummaryEmailSender.java @@ -25,21 +25,24 @@ public class DailyChallengeSummaryEmailSender { private Boolean enableJobAlert; @Scheduled(cron = "${scheduled.cron.dailyChallengeSummary}") - public void notifyRegistrantAboutChallengeTimeline() throws Exception { + public void sendDailyEmailAboutChallengeSummary() throws Exception { if (enableJobAlert) { List challengePhases = Arrays.asList(ChallengePhaseEnum.REGISTRATION, ChallengePhaseEnum.IN_PROGRESS); + int count = 0; for (ChallengePhaseEnum challengePhase : challengePhases) { List challengeEntities = challengeService.listChallengesByPhase(challengePhase); for (ChallengeEntity challengeEntity : challengeEntities) { try { challengeService.sendDailySummaryEmailToChallengeOwner(challengeEntity); + count++; } catch (Exception ex) { LOGGER.error(ex.getMessage(), ex); } } } + LOGGER.info("There are " + count + " daily emails has been sent about challenge summary"); } } } diff --git a/src/main/java/com/techlooper/cron/JobAlertEmailSender.java b/src/main/java/com/techlooper/cron/JobAlertEmailSender.java index 2bcc183f0..a67b9f829 100644 --- a/src/main/java/com/techlooper/cron/JobAlertEmailSender.java +++ b/src/main/java/com/techlooper/cron/JobAlertEmailSender.java @@ -6,6 +6,8 @@ import com.techlooper.model.JobSearchResponse; import com.techlooper.service.JobAggregatorService; import org.dozer.Mapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -25,6 +27,8 @@ @Service public class JobAlertEmailSender { + private final static Logger LOGGER = LoggerFactory.getLogger(JobAlertEmailSender.class); + @Value("${jobAlert.enable}") private Boolean enableJobAlert; @@ -48,16 +52,19 @@ public void sendJobAlertEmail() throws Exception { jobAggregatorService.findJobAlertRegistration(JobAlertPeriodEnum.WEEKLY); if (!jobAlertRegistrationEntities.isEmpty()) { + int count = 0; for (JobAlertRegistrationEntity jobAlertRegistrationEntity : jobAlertRegistrationEntities) { JobSearchCriteria criteria = dozerMapper.map(jobAlertRegistrationEntity, JobSearchCriteria.class); JobSearchResponse jobSearchResponse = jobAggregatorService.findJob(criteria); if (jobSearchResponse.getTotalJob() > 0) { jobAggregatorService.sendEmail(jobAlertRegistrationEntity, jobSearchResponse); jobAggregatorService.updateSendEmailResultCode(jobAlertRegistrationEntity, EMAIL_SENT); + count++; } else { jobAggregatorService.updateSendEmailResultCode(jobAlertRegistrationEntity, JOB_NOT_FOUND); } } + LOGGER.info("There are " + count + " job alert emails has been sent"); } } } diff --git a/src/main/java/com/techlooper/model/EmailContent.java b/src/main/java/com/techlooper/model/EmailContent.java new file mode 100644 index 000000000..45435e21a --- /dev/null +++ b/src/main/java/com/techlooper/model/EmailContent.java @@ -0,0 +1,50 @@ +package com.techlooper.model; + +import javax.mail.Address; +import java.io.Serializable; + +/** + * Created by phuonghqh on 10/1/15. + */ +public class EmailContent implements Serializable { + + private String subject; + + private String content; + + private Address[] recipients; + + private Language language; + + public Language getLanguage() { + return language; + } + + public void setLanguage(Language language) { + this.language = language; + } + + public Address[] getRecipients() { + return recipients; + } + + public void setRecipients(Address[] recipients) { + this.recipients = recipients; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/com/techlooper/service/ChallengeService.java b/src/main/java/com/techlooper/service/ChallengeService.java index 6147739e1..76ebcd4ca 100644 --- a/src/main/java/com/techlooper/service/ChallengeService.java +++ b/src/main/java/com/techlooper/service/ChallengeService.java @@ -4,10 +4,7 @@ import com.techlooper.entity.ChallengeRegistrantDto; import com.techlooper.entity.ChallengeRegistrantEntity; import com.techlooper.entity.ChallengeSubmissionEntity; -import com.techlooper.model.ChallengeDetailDto; -import com.techlooper.model.ChallengeDto; -import com.techlooper.model.ChallengePhaseEnum; -import com.techlooper.model.TimePeriodEnum; +import com.techlooper.model.*; import freemarker.template.TemplateException; import javax.mail.MessagingException; @@ -22,64 +19,71 @@ */ public interface ChallengeService { - ChallengeEntity savePostChallenge(ChallengeDto challengeDto) throws Exception; + ChallengeEntity savePostChallenge(ChallengeDto challengeDto) throws Exception; - void sendPostChallengeEmailToEmployer(ChallengeEntity challengeEntity) - throws MessagingException, IOException, TemplateException; + void sendPostChallengeEmailToEmployer(ChallengeEntity challengeEntity) + throws MessagingException, IOException, TemplateException; - void sendPostChallengeEmailToTechloopies(ChallengeEntity challengeEntity, Boolean isNewChallenge) - throws MessagingException, IOException, TemplateException; + void sendPostChallengeEmailToTechloopies(ChallengeEntity challengeEntity, Boolean isNewChallenge) + throws MessagingException, IOException, TemplateException; - void sendEmailNotifyRegistrantAboutChallengeTimeline(ChallengeEntity challengeEntity, - ChallengeRegistrantEntity challengeRegistrantEntity, ChallengePhaseEnum challengePhase) throws Exception; + void sendEmailNotifyRegistrantAboutChallengeTimeline(ChallengeEntity challengeEntity, + ChallengeRegistrantEntity challengeRegistrantEntity, ChallengePhaseEnum challengePhase) throws Exception; - ChallengeDetailDto getChallengeDetail(Long challengeId); + ChallengeDetailDto getChallengeDetail(Long challengeId); - Long getNumberOfRegistrants(Long challengeId); + Long getNumberOfRegistrants(Long challengeId); - void sendApplicationEmailToContestant(ChallengeEntity challengeEntity, ChallengeRegistrantEntity challengeRegistrantEntity) - throws MessagingException, IOException, TemplateException; + void sendApplicationEmailToContestant(ChallengeEntity challengeEntity, ChallengeRegistrantEntity challengeRegistrantEntity) + throws MessagingException, IOException, TemplateException; - void sendApplicationEmailToEmployer(ChallengeEntity challengeEntity, ChallengeRegistrantEntity challengeRegistrantEntity) - throws MessagingException, IOException, TemplateException; + void sendApplicationEmailToEmployer(ChallengeEntity challengeEntity, ChallengeRegistrantEntity challengeRegistrantEntity) + throws MessagingException, IOException, TemplateException; - long joinChallenge(ChallengeRegistrantDto challengeRegistrantDto) throws MessagingException, IOException, TemplateException; + long joinChallenge(ChallengeRegistrantDto challengeRegistrantDto) throws MessagingException, IOException, TemplateException; - List listChallenges(); + List listChallenges(); - List listChallenges(String ownerEmail); + List listChallenges(String ownerEmail); - List listChallengesByPhase(ChallengePhaseEnum challengePhase); + List listChallengesByPhase(ChallengePhaseEnum challengePhase); - Long getTotalNumberOfChallenges(); + Long getTotalNumberOfChallenges(); - Double getTotalAmountOfPrizeValues(); + Double getTotalAmountOfPrizeValues(); - Long getTotalNumberOfRegistrants(); + Long getTotalNumberOfRegistrants(); - ChallengeDetailDto getTheLatestChallenge(); + ChallengeDetailDto getTheLatestChallenge(); - Collection findByOwnerAndCondition(String owner, Predicate condition); + Collection findByOwnerAndCondition(String owner, Predicate condition); - Collection findInProgressChallenges(String owner); + Collection findInProgressChallenges(String owner); // Collection findRegistrantsByChallengeId(Long challengeId); - Long countRegistrantsByChallengeId(Long challengeId); + Long countRegistrantsByChallengeId(Long challengeId); - boolean delete(Long id, String ownerEmail); + boolean delete(Long id, String ownerEmail); - ChallengeDto findChallengeById(Long id); + ChallengeDto findChallengeById(Long id); - Set findRegistrantsByOwner(String ownerEmail, Long challengeId); + Set findRegistrantsByOwner(String ownerEmail, Long challengeId); - ChallengeRegistrantDto saveRegistrant(String ownerEmail, ChallengeRegistrantDto challengeRegistrantDto); + ChallengeRegistrantDto saveRegistrant(String ownerEmail, ChallengeRegistrantDto challengeRegistrantDto); - List findChallengeRegistrantWithinPeriod( - Long challengeId, Long currentDateTime, TimePeriodEnum period); + List findChallengeRegistrantWithinPeriod( + Long challengeId, Long currentDateTime, TimePeriodEnum period); - List findChallengeSubmissionWithinPeriod( - Long challengeId, Long currentDateTime, TimePeriodEnum period); + List findChallengeSubmissionWithinPeriod( + Long challengeId, Long currentDateTime, TimePeriodEnum period); + + void sendDailySummaryEmailToChallengeOwner(ChallengeEntity challengeEntity) throws Exception; + + boolean isOwnerOfChallenge(String ownerEmail, Long challengeId); + + boolean sendEmailToDailyChallengeRegistrants(String challengeOwner, Long challengeId, Long now, EmailContent emailContent); + + boolean sendEmailToRegistrant(String challengeOwner, Long challengeId, Long registrantId, EmailContent emailContent); - void sendDailySummaryEmailToChallengeOwner(ChallengeEntity challengeEntity) throws Exception; } diff --git a/src/main/java/com/techlooper/service/EmailService.java b/src/main/java/com/techlooper/service/EmailService.java new file mode 100644 index 000000000..181b55bdb --- /dev/null +++ b/src/main/java/com/techlooper/service/EmailService.java @@ -0,0 +1,11 @@ +package com.techlooper.service; + +import com.techlooper.model.EmailContent; + +/** + * Created by phuonghqh on 10/1/15. + */ +public interface EmailService { + + boolean sendEmail(EmailContent emailContent); +} diff --git a/src/main/java/com/techlooper/service/impl/ChallengeServiceImpl.java b/src/main/java/com/techlooper/service/impl/ChallengeServiceImpl.java index 77cab5637..b341341fa 100644 --- a/src/main/java/com/techlooper/service/impl/ChallengeServiceImpl.java +++ b/src/main/java/com/techlooper/service/impl/ChallengeServiceImpl.java @@ -9,6 +9,7 @@ import com.techlooper.repository.elasticsearch.ChallengeRepository; import com.techlooper.repository.elasticsearch.ChallengeSubmissionRepository; import com.techlooper.service.ChallengeService; +import com.techlooper.service.EmailService; import com.techlooper.util.DateTimeUtils; import freemarker.template.Template; import freemarker.template.TemplateException; @@ -24,6 +25,8 @@ import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; @@ -59,6 +62,8 @@ @Service public class ChallengeServiceImpl implements ChallengeService { + private final static Logger LOGGER = LoggerFactory.getLogger(ChallengeServiceImpl.class); + @Resource private ElasticsearchTemplate elasticsearchTemplateUserImport; @@ -167,6 +172,9 @@ public class ChallengeServiceImpl implements ChallengeService { @Resource private Template dailyChallengeSummaryMailTemplateEn; + @Resource + private EmailService emailService; + public ChallengeEntity savePostChallenge(ChallengeDto challengeDto) throws Exception { ChallengeEntity challengeEntity = dozerMapper.map(challengeDto, ChallengeEntity.class); if (challengeDto.getChallengeId() == null) { @@ -233,6 +241,8 @@ public void sendEmailNotifyRegistrantAboutChallengeTimeline(ChallengeEntity chal stringWriter.flush(); postChallengeMailMessage.saveChanges(); mailSender.send(postChallengeMailMessage); + LOGGER.info(postChallengeMailMessage.getMessageID() + " has been sent to users " + + postChallengeMailMessage.getAllRecipients() + " with challengeId = " + challengeEntity.getChallengeId()); } private String getNotifyRegistrantChallengeTimelineSubject( @@ -705,4 +715,38 @@ public void sendDailySummaryEmailToChallengeOwner(ChallengeEntity challengeEntit mailSender.send(postChallengeMailMessage); } + public boolean isOwnerOfChallenge(String ownerEmail, Long challengeId) { + ChallengeEntity challenge = challengeRepository.findOne(challengeId); + return challenge.getAuthorEmail().equalsIgnoreCase(ownerEmail); + } + + public boolean sendEmailToDailyChallengeRegistrants(String challengeOwner, Long challengeId, Long now, EmailContent emailContent) { + if (isOwnerOfChallenge(challengeOwner, challengeId)) { + List registrants = findChallengeRegistrantWithinPeriod(challengeId, now, TimePeriodEnum.TWENTY_FOUR_HOURS); + String csvEmails = registrants.stream().map(ChallengeRegistrantEntity::getRegistrantEmail).distinct().collect(Collectors.joining(",")); + try { + emailContent.setRecipients(InternetAddress.parse(csvEmails)); + } catch (AddressException e) { + LOGGER.debug("Can not parse email address", e); + return false; + } + } + return emailService.sendEmail(emailContent); + } + + public boolean sendEmailToRegistrant(String challengeOwner, Long challengeId, Long registrantId, EmailContent emailContent) { + if (isOwnerOfChallenge(challengeOwner, challengeId)) { + ChallengeRegistrantEntity registrant = challengeRegistrantRepository.findOne(registrantId); + String csvEmails = registrant.getRegistrantEmail(); + try { + emailContent.setRecipients(InternetAddress.parse(csvEmails)); + } + catch (AddressException e) { + LOGGER.debug("Can not parse email address", e); + return false; + } + } + return emailService.sendEmail(emailContent); + } + } diff --git a/src/main/java/com/techlooper/service/impl/EmailServiceImpl.java b/src/main/java/com/techlooper/service/impl/EmailServiceImpl.java new file mode 100644 index 000000000..0e82dd19d --- /dev/null +++ b/src/main/java/com/techlooper/service/impl/EmailServiceImpl.java @@ -0,0 +1,69 @@ +package com.techlooper.service.impl; + +import com.techlooper.model.EmailContent; +import com.techlooper.model.Language; +import com.techlooper.service.EmailService; +import freemarker.template.Template; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.mail.Message; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeUtility; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by phuonghqh on 10/1/15. + */ +@Service +public class EmailServiceImpl implements EmailService { + + private final Logger LOGGER = LoggerFactory.getLogger(EmailServiceImpl.class); + + @Resource + private JavaMailSender mailSender; + + @Resource + private MimeMessage fromTechlooperMailMessage; + + @Resource + private Template challengeEmployerMailTemplateEn; + + @Resource + private Template challengeEmployerMailTemplateVi; + + @Value("${web.baseUrl}") + private String webBaseUrl; + + public boolean sendEmail(EmailContent emailContent) { + try { + String subject = StringUtils.isNotEmpty(emailContent.getSubject()) ? emailContent.getSubject() : "[No Subject]"; + fromTechlooperMailMessage.setSubject(MimeUtility.encodeText(subject, "UTF-8", null)); + + Template template = emailContent.getLanguage() == Language.vi ? challengeEmployerMailTemplateVi : + challengeEmployerMailTemplateEn; + + StringWriter stringWriter = new StringWriter(); + Map templateModel = new HashMap<>(); + templateModel.put("webBaseUrl", webBaseUrl); + templateModel.put("emailContent", emailContent.getContent()); + template.process(templateModel, stringWriter); + + fromTechlooperMailMessage.setText(stringWriter.toString(), "UTF-8", "html"); + fromTechlooperMailMessage.setRecipients(Message.RecipientType.TO, emailContent.getRecipients()); + fromTechlooperMailMessage.saveChanges(); + mailSender.send(fromTechlooperMailMessage); + } catch (Exception e) { + LOGGER.error("Can not send email", e); + return false; + } + return true; + } +} diff --git a/src/main/resources/local/logback.groovy b/src/main/resources/local/logback.groovy index a06613303..500e3fafb 100644 --- a/src/main/resources/local/logback.groovy +++ b/src/main/resources/local/logback.groovy @@ -5,6 +5,7 @@ import ch.qos.logback.core.rolling.RollingFileAppender import ch.qos.logback.core.rolling.TimeBasedRollingPolicy import static ch.qos.logback.classic.Level.ALL +import static ch.qos.logback.classic.Level.DEBUG import static ch.qos.logback.classic.Level.ERROR import static ch.qos.logback.classic.Level.OFF @@ -25,8 +26,14 @@ appender("ROOT_FILE", RollingFileAppender) { } } +appender("CONSOLE", ConsoleAppender) { + encoder(PatternLayoutEncoder) { + pattern = "%d{dd-MM-yyyy HH:mm:ss.SSS} %p [%t] %c{1}: %m%n" + } +} + logger("org.elasticsearch", ERROR) logger("org.hibernate", ERROR) logger("org.dozer", ERROR) -root(ALL, ["ROOT_FILE"]) \ No newline at end of file +root(DEBUG, ["CONSOLE"]) \ No newline at end of file diff --git a/src/main/resources/template/challengeDailySummary.en.ftl b/src/main/resources/template/challengeDailySummary.en.ftl index 3effb3dd5..f4980475b 100644 --- a/src/main/resources/template/challengeDailySummary.en.ftl +++ b/src/main/resources/template/challengeDailySummary.en.ftl @@ -322,18 +322,18 @@ - +
${latestSubmission_index + 1} ${latestSubmission.registrantName}
- +
Review - Feedback + Feedback
diff --git a/src/main/resources/template/challengeDailySummary.vi.ftl b/src/main/resources/template/challengeDailySummary.vi.ftl index 28a006a21..bfce94669 100644 --- a/src/main/resources/template/challengeDailySummary.vi.ftl +++ b/src/main/resources/template/challengeDailySummary.vi.ftl @@ -2,652 +2,409 @@ - - - - - - + @media (device-width: 320px) { + body { + max-width: 100% !important; + } + [class=hidden] { + display: none + } + [class=body-content] { + max-width: 100% !important; + width: 100% !important; + } + p[class=bottom-links] span { + display: block; + } + [class=cta-large] { + font-size: 24px !important; + } + [class=candidate] td { + font-size: 24px !important; + line-height: 30px !important; + } + [class=content-padding] { + padding: 20px !important; + } + [class=email] { + color: #00b9f2; + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + vertical-align: middle; + width: 240px; + } + [class=small-text] { + font-size: 13px !important; + white-space: nowrap !important + } + [class=logo] img { + width: 100px; + } + [class=fullWidth] { + width: 100% !important; + } + [class=btnFull] { + width: 100% !important; + padding: 10px 0 !important + } + .fullWidth { + width: 100% !important; + } + } + + - -
- - - - -
-
- - + +
+ + + - - - -
- - - - - - - -
- - - - - -
- - - - - + +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Thử Thách Trực Tuyến

-
-

Xây Dựng Đội Ngũ Phát Triển Sản Phẩm Của Chính Bạn Thông Qua Các Cuộc Thi Trực Tuyến.

-
- -
-

Xin chào thu.hoang@navigosgroup.com,

-
- -
-

Dưới đây là báo cáo hằng ngày cho thử thách Challenge Name

-
- -
- - - - - - - - - - -
- - - - -
Thử thách của bạn mở rộng như thế nào trong 24 giờ qua
- - - - - - - -
- -
- Bạn nhận được thêm 5 đăng ký. Các thí sinh mới tham gia bao gồm: -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
01Johnson Pham
- - - -
02Huynh Phuong
- - - -
03Huynh Phuong
- - - -
04Huynh Phuong
- - - -
05Huynh Phuong
- - - -
-
- - - - -
- Gửi Email Cho Tất Cả -
-
- -
-
- -
- - - - -
- - - - -
Đây có phải là thời điểm để quảng bá cho thử thách của bạn?
- - - - - - - - - - -
- -
Nếu bạn muốn nhận thêm nhiều lượt đăng ký, trả lời email này để chúng tôi có thể hỗ trợ bạn.
- -
-
-
- -
- - - - + + +
- - - - -
Khám phá số lượng bài nộp mới vào thử thách
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- Bạn nhận được thêm 10 bài nộp mới cho giai đoạn "Ý Tưởng" trong vòng 24 giờ qua: -
- -
- - - - - -
01Johnson Pham
- - - - - - -
- XemPhản hồi
-
- -
- - - - - -
02Johnson Pham
- - - - - - -
- XemPhản hồi
-
- -
- - - - - -
03Johnson Pham
- - - - - - -
- XemPhản hồi
-
- -
- - - - - -
04Johnson Pham
- - - - + - - - - - -
- XemPhản hồi +
+ + + + + + +
+ + + + + + + +
+ + + + + +
+
+ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#if latestSubmissions?has_content> + + + + + + + + + + + + +
+

Thử Thách Trực Tuyến

+
+

Xây Dựng Đội Ngũ Phát Triển Sản Phẩm Của Chính Bạn Thông Qua Các Cuộc Thi Trực Tuyến.

+
+ +
+

Xin chào,

+
+ +
+

Dưới đây là báo cáo hằng ngày cho thử thách ${challengeName} trong vòng 24 giờ qua

+
+ +
+ + + + + <#if latestRegistrants?has_content> + + + + + + + +
+ + + + +
Thử thách của bạn mở rộng như thế nào trong 24 giờ qua
+ + + + + + + +
+ +
+ Bạn nhận được thêm ${numberOfRegistrants} đăng ký mới +
+ <#if latestRegistrants?has_content> + + <#list latestRegistrants as registrant> + + + + + + + + + + + + + +
+ + + +
${registrant_index + 1}${registrant.registrantFirstName} ${registrant.registrantLastName}
+ + + +
+ +
+ + + + +
+ Gửi Email Cho Tất Cả +
+
+ +
+
+ +
+ + + + +
+ + + + +
Đây có phải là thời điểm để quảng bá cho thử thách của bạn?
+ + + + + + + + + + +
+ +
Nếu bạn muốn nhận thêm nhiều lượt đăng ký, trả lời email này để chúng tôi có thể hỗ trợ bạn.
+ +
+
+
+ +
+ + + + - + + + +
+ + + + +
Khám phá số lượng bài nộp mới vào thử thách
+ + + + + + + + <#list latestSubmissions as latestSubmission> + + + + + + + +
+ +
+ Bạn nhận được thêm ${numberOfSubmissions} bài nộp mới : +
+ +
+ + + + + +
${latestSubmission_index + 1}${latestSubmission.registrantName}
+ + + + + +
+ Xem Trước + Phản Hồi +
+
+
+ +
+
+ +
+ + + + +
+ +
+
+ + + + + + + +
+

KẾT NỐI VỚI CHÚNG TÔI

+
+ + LinkedIn + +
+ + + + + +
+
-
- -
- - - - - -
05Johnson Pham
- - - - - - -
- XemPhản hồi
-
- -
- - - - - -
06Johnson Pham
- - - - - - -
- XemPhản hồi
-
- -
- - - - - -
07Johnson Pham
- - - - - - -
- XemPhản hồi
-
- -
- - - - - -
08Johnson Pham
- - - - - - -
- XemPhản hồi
-
- -
- - - - - -
09Johnson Pham
- - - - - - -
- XemPhản hồi
-
- -
- - - - - -
10Johnson Pham
- - - - - - -
- XemPhản hồi
-
-
- -
-
- -
- - - - + +
- -
-
- - - - - - - -
-

KẾT NỐI VỚI CHÚNG TÔI

-
- - LinkedIn - -
- - - - -
-
-
-
-
diff --git a/src/main/resources/template/challengeEmployerEmail.en.ftl b/src/main/resources/template/challengeEmployerEmail.en.ftl index 18b20b2fd..18eebcc51 100644 --- a/src/main/resources/template/challengeEmployerEmail.en.ftl +++ b/src/main/resources/template/challengeEmployerEmail.en.ftl @@ -116,12 +116,12 @@ @@ -161,22 +161,12 @@ - - - - - -
-

Hey thu.hoang@navigosgroup.com,

-
- -
- email challenge container will be show here + ${emailContent}
diff --git a/src/main/resources/template/challengeEmployerEmail.vi.ftl b/src/main/resources/template/challengeEmployerEmail.vi.ftl index 0af2c4f69..a875ca623 100644 --- a/src/main/resources/template/challengeEmployerEmail.vi.ftl +++ b/src/main/resources/template/challengeEmployerEmail.vi.ftl @@ -116,12 +116,12 @@ @@ -161,22 +161,12 @@ - - - - - -
-

Xin chào thu.hoang@navigosgroup.com,

-
- -
- email challenge container will be show here + ${emailContent}
diff --git a/src/main/webapp/assets/modules/_app.js b/src/main/webapp/assets/modules/_app.js index a58a470c2..9655e461c 100644 --- a/src/main/webapp/assets/modules/_app.js +++ b/src/main/webapp/assets/modules/_app.js @@ -211,7 +211,7 @@ techlooper.config(["$routeProvider", "$translateProvider", "$authProvider", "loc .otherwise({ redirectTo: function (err, path, params) { if (!$.isEmptyObject(params)) { - return "/loading"; + return "/loading?" + $.param(params); } if (window.location.host.indexOf("hiring") >= 0) { diff --git a/src/main/webapp/assets/modules/common/apiService.js b/src/main/webapp/assets/modules/common/apiService.js index be1a8393a..2c3a5ecc5 100644 --- a/src/main/webapp/assets/modules/common/apiService.js +++ b/src/main/webapp/assets/modules/common/apiService.js @@ -1,4 +1,4 @@ -techlooper.factory("apiService", function ($rootScope, $location, jsonValue, $http, localStorageService, utils) { +techlooper.factory("apiService", function ($rootScope, $location, jsonValue, $http, localStorageService, utils, $translate) { var instance = { login: function (techlooperKey) { @@ -176,31 +176,61 @@ techlooper.factory("apiService", function ($rootScope, $location, jsonValue, $ht /** * @see com.techlooper.controller.ChallengeController.deleteChallengeById * */ - deleteChallengeById: function(id) { + deleteChallengeById: function (id) { return $http.delete("challenge/" + id); }, /** * @see com.techlooper.controller.ChallengeController.findChallengeById * */ - findChallengeById: function(id) { + findChallengeById: function (id) { return $http.get("challenges/" + id); }, /** * @see com.techlooper.controller.ChallengeController.getRegistrantsById * */ - getChallengeRegistrants: function(challengeId) { + getChallengeRegistrants: function (challengeId) { return $http.get("challenges/" + challengeId + "/registrants"); }, /** * @see com.techlooper.controller.ChallengeController.saveRegistrant * */ - saveChallengeRegistrant: function(registrant) { + saveChallengeRegistrant: function (registrant) { return $http.post("challengeDetail/registrant", registrant); + }, + + /** + * @see com.techlooper.controller.UserController.getDailyChallengeRegistrantNames + * */ + getDailyChallengeRegistrantNames: function (challengeId, now) { + return $http.get("user/challengeRegistrantNames/" + challengeId + "/" + now); + }, + + /** + * @see com.techlooper.controller.UserController.sendEmailToDailyChallengeRegistrants + * */ + sendEmailToDailyChallengeRegistrants: function (challengeId, now, emailContent) { + emailContent.language = $translate.use(); + return $http.post("user/challenge/sendMailToDaily/" + challengeId + "/" + now, emailContent); + }, + + /** + * @see com.techlooper.controller.ChallengeController.getChallengeRegistrant + * */ + getChallengeRegistrantFullName: function (challengeRegistrantId) { + return $http.get("challengeRegistrant/fullName/" + challengeRegistrantId, {transformResponse: function (d, h) {return d;}}); + }, + + /** + * @see com.techlooper.controller.UserController.sendFeedbackToRegistrant + * */ + sendFeedbackToRegistrant: function(challengeId, registrantId, emailContent) { + emailContent.language = $translate.use(); + return $http.post("user/challenge/feedback/" + challengeId + "/" + registrantId, emailContent); } - } + }; return instance; }); \ No newline at end of file diff --git a/src/main/webapp/assets/modules/common/popupEmail.html b/src/main/webapp/assets/modules/common/popupEmail.html new file mode 100644 index 000000000..875135968 --- /dev/null +++ b/src/main/webapp/assets/modules/common/popupEmail.html @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/modules/common/popupEmailDirective.js b/src/main/webapp/assets/modules/common/popupEmailDirective.js new file mode 100644 index 000000000..38eba2439 --- /dev/null +++ b/src/main/webapp/assets/modules/common/popupEmailDirective.js @@ -0,0 +1,25 @@ +techlooper.directive("popupEmail", function () { + return { + restrict: "E", + replace: true, + templateUrl: "modules/common/popupEmail.html", + scope: { + composeEmail: "=" + }, + link: function (scope, element, attr, ctrl) { + $('.summernote').summernote({ + toolbar: [ + ['fontname', ['fontname']], + ['fontsize', ['fontsize']], + ['style', ['bold', 'italic', 'underline', 'clear']], + ['color', ['color']], + ['para', ['ul', 'ol', 'paragraph']], + ['height', ['height']], + ['table', ['table']], + ['insert', ['link']], + ['misc', ['undo', 'redo', 'codeview', 'fullscreen']] + ] + }); + } + } +}); \ No newline at end of file diff --git a/src/main/webapp/assets/modules/contest-detail/contest-detail.tem.html b/src/main/webapp/assets/modules/contest-detail/contest-detail.tem.html index c7b6b2120..4bc9bcc11 100644 --- a/src/main/webapp/assets/modules/contest-detail/contest-detail.tem.html +++ b/src/main/webapp/assets/modules/contest-detail/contest-detail.tem.html @@ -80,29 +80,33 @@

ng-class="{'three-cols': contestDetail.timeline == 3, 'four-cols': contestDetail.timeline == 4, 'five-cols': contestDetail.timeline == 5}"> - {{contestDetail.startDateTime}} - {{contestDetail.registrationDateTime}} + + {{contestDetail.startDateTime}} + {{contestDetail.registrationDateTime}}
- 15/09/2015 + + {{contestDetail.ideaSubmissionDateTime}}
- 15/09/2015 + + {{contestDetail.uxSubmissionDateTime}}
- 15/09/2015 + + {{contestDetail.prototypeSubmissionDateTime}}
@@ -113,7 +117,8 @@

ng-class="{'three-cols': contestDetail.timeline == 3, 'four-cols': contestDetail.timeline == 4, 'five-cols': contestDetail.timeline == 5}"> - {{contestDetail.submissionDateTime}} + + {{contestDetail.submissionDateTime}} diff --git a/src/main/webapp/assets/modules/employer-dashboard/employer-dashboard.html b/src/main/webapp/assets/modules/employer-dashboard/employer-dashboard.html index f063039f7..790153fd2 100644 --- a/src/main/webapp/assets/modules/employer-dashboard/employer-dashboard.html +++ b/src/main/webapp/assets/modules/employer-dashboard/employer-dashboard.html @@ -1,5 +1,4 @@
- Open Email Compose
@@ -163,43 +162,6 @@

- + diff --git a/src/main/webapp/assets/modules/employer-dashboard/employerDashboardController.js b/src/main/webapp/assets/modules/employer-dashboard/employerDashboardController.js index b1bf6de19..eceb34275 100644 --- a/src/main/webapp/assets/modules/employer-dashboard/employerDashboardController.js +++ b/src/main/webapp/assets/modules/employer-dashboard/employerDashboardController.js @@ -1,20 +1,55 @@ -techlooper.controller('employerDashboardController', function ($scope, jsonValue, utils, apiService, $location, $filter) { - $('.summernote').summernote(); - var edit = function() { - $('.click2edit').summernote({ - focus: true - }); - }; - var save = function() { - var aHTML = $('.click2edit').code(); //save HTML If you need(aHTML: array). - $('.click2edit').destroy(); - }; +techlooper.controller('employerDashboardController', function ($scope, jsonValue, utils, apiService, $location, $filter, $route) { utils.sendNotification(jsonValue.notifications.loading, $(window).height()); + $scope.composeEmail = { + send: function () { + $scope.composeEmail.content = $('.summernote').code(); + if($scope.composeEmail.content == '


'){ + return; + } + if ($scope.composeEmail.action == "challenge-daily-mail-registrants") { + apiService.sendEmailToDailyChallengeRegistrants($scope.composeEmail.challengeId, $scope.composeEmail.now, $scope.composeEmail) + .finally(function () { + $scope.composeEmail.cancel(); + }); + } + else if ($scope.composeEmail.action == "feedback-registrant") { + apiService.sendFeedbackToRegistrant($scope.composeEmail.challengeId, $scope.composeEmail.registrantId, $scope.composeEmail) + .finally(function () { + $scope.composeEmail.cancel(); + }); + } + }, + cancel: function () { + $location.search({}); + $('.modal-backdrop').remove(); + } + }; + var param = $location.search(); - if (!$.isEmptyObject(param) && param.a == "challenge-daily-mail-registrants") { - //TODO send email to all new registrants + if (!$.isEmptyObject(param)) { + $scope.composeEmail.action = param.a; + if (param.a == "challenge-daily-mail-registrants") { + var challengeId = param.challengeId; + var now = param.currentDateTime; + $scope.composeEmail.challengeId = challengeId; + $scope.composeEmail.now = now; + apiService.getDailyChallengeRegistrantNames(challengeId, now) + .success(function (names) { + $scope.composeEmail.names = names.join("; "); + $("#emailCompose").modal(); + }); + } + else if (param.a == "feedback-registrant") { + $scope.composeEmail.challengeId = param.challengeId; + $scope.composeEmail.registrantId = param.registrantId; + apiService.getChallengeRegistrantFullName($scope.composeEmail.registrantId) + .success(function (fullname) { + $scope.composeEmail.names = fullname; + $("#emailCompose").modal(); + }); + } } apiService.getEmployerDashboardInfo() @@ -23,7 +58,9 @@ techlooper.controller('employerDashboardController', function ($scope, jsonValue data.projects.sort(function (left, right) {return right.projectId - left.projectId;}); $scope.dashboardInfo = data; }) - .finally(function () {utils.sendNotification(jsonValue.notifications.loaded, $(window).height());}); + .finally(function () { + utils.sendNotification(jsonValue.notifications.loaded, $(window).height()); + }); $scope.changeChallengeStatus = function (status) { $scope.challengeStatus = status; diff --git a/src/main/webapp/assets/modules/loading-box/loadingBoxController.js b/src/main/webapp/assets/modules/loading-box/loadingBoxController.js index 8b3f17667..e0b343f06 100644 --- a/src/main/webapp/assets/modules/loading-box/loadingBoxController.js +++ b/src/main/webapp/assets/modules/loading-box/loadingBoxController.js @@ -10,5 +10,28 @@ techlooper.controller("loadingBoxController", function (utils, jsonValue, $scope return securityService.routeByRole(); } + var param = $location.search(); + if (!$.isEmptyObject(param)) { + switch (param.action) { + case "registerVnwUser": + localStorageService.set("lastName", param.lastName); + localStorageService.set("firstName", param.firstName); + localStorageService.set("email", param.email); + break; + + case "loginBySocial": + securityService.login(param.code, param.social, param.social); + break; + + case "redirectJA": + window.location.href = param.targetUrl; + break; + + case "cancel": + $location.url("/"); + break; + } + } + $location.url("/"); }); \ No newline at end of file diff --git a/src/main/webapp/assets/modules/post-contest/postContestController.js b/src/main/webapp/assets/modules/post-contest/postContestController.js index a84bb6745..208c2d296 100644 --- a/src/main/webapp/assets/modules/post-contest/postContestController.js +++ b/src/main/webapp/assets/modules/post-contest/postContestController.js @@ -256,5 +256,8 @@ techlooper.controller("postContestController", function ($scope, $http, jsonValu compareDate = compareDate.charAt(0).toUpperCase() + compareDate.substr(1); $scope.timelineForm && $scope.timelineForm.submissionDate.$setValidity("gt" + compareDate, valid); + if (!$scope.ideaChecked) delete $scope.contest.ideaSubmissionDate; + if (!$scope.uiuxChecked) delete $scope.contest.uxSubmissionDate; + if (!$scope.prototypeChecked) delete $scope.contest.prototypeSubmissionDate; }, true); }); \ No newline at end of file diff --git a/src/main/webapp/assets/modules/post-contest/postContestTimeline.html b/src/main/webapp/assets/modules/post-contest/postContestTimeline.html index 3778e221a..5781ff4d6 100644 --- a/src/main/webapp/assets/modules/post-contest/postContestTimeline.html +++ b/src/main/webapp/assets/modules/post-contest/postContestTimeline.html @@ -6,7 +6,7 @@
-
-
  • - +
    @@ -65,8 +67,8 @@

    + data-toggle="tooltip" data-original-title="{{'challengeIdeaTips' | translate}}" + ng-model="contest.ideaSubmissionDate" ng-required="ideaChecked"> @@ -76,13 +78,16 @@

+
  • - +
    @@ -90,8 +95,8 @@

    + data-toggle="tooltip" data-original-title="{{'challengeUIUXTips' | translate}}" + ng-model="contest.uxSubmissionDate" ng-required="uiuxChecked"> @@ -101,6 +106,7 @@

  • +
  • - +
    @@ -117,8 +125,8 @@

    + data-toggle="tooltip" data-original-title="{{'challengePrototypeTips' | translate}}" + ng-model="contest.prototypeSubmissionDate" ng-required="prototypeChecked"> @@ -128,6 +136,7 @@

  • +
    - *", "challengeRegisterTip": "Typically between 2-4 weeks after the challenge start date. After this date, the contest is officially in-progress and contestants are working on the challenge. It is up to you to accept more registrations at this stage.", - "challengeSubmit": "And Contestants Must Submit By: *", + "challengeSubmit": "And Contestants Must Submit Final App By:: *", "challengeSubmitTip": "Contest duration should not be longer than 8 weeks. This date officially ends the contest. All the contestants must submit their solutions by this date.", "userName": "Enter User Name/Email Address: *", "userNameEX": "Username / Example@vietnamworks.com", @@ -729,7 +729,7 @@ "disqualify": "Disqualify", "qualify": "Qualify", "qualifyConfirmation": "Are you sure to qualify this contestant again?", - "mileStones": "Mile Stones:", + "mileStones": "Milestones:", "pleaseSelectMileStone": "Please select milestones relevant to your contest.", "prototype": "Prototype", "idea": "Idea", @@ -750,5 +750,8 @@ "finalDateGtRegisterDate": "Final submission day should be ahead of Register day at least 1 day.", "mailSubject": "Subject:", "mailTo": "To:", - "mailSend": "Send" + "mailSend": "Send", + "challengeIdeaTips": "This date officially ends the Idea phase of the contest. Contestants must propose their ideas to Contest Owner by this date. Once the Contest Owner approves the idea, contestant who proposed the idea will be selected to go to the next phase.", + "challengeUIUXTips": "This date officially ends the UI/UX phase of the contest. Contestants must propose their UI/UX mockups and/or Usability Test if required to Contest Owner by this date. Once the Contest Owner approves the mockup, contestant who submitted that mockup will be selected to go to the next phase.", + "challengePrototypeTips": "This date officially ends the Prototype phase of the contest. Contestants must submit their prototypes to Contest Owner by this date. Once the Contest Owner approves the prototype, contestant who submitted that prototype will be selected to go to the next phase." } \ No newline at end of file diff --git a/src/main/webapp/assets/modules/translation/messages_vi.json b/src/main/webapp/assets/modules/translation/messages_vi.json index 38d22b4c0..0106da55a 100644 --- a/src/main/webapp/assets/modules/translation/messages_vi.json +++ b/src/main/webapp/assets/modules/translation/messages_vi.json @@ -354,7 +354,7 @@ "challengeStartTip": "Đây là Ngày Bắt Đầu cuộc thi và cũng là ngày bắt đầu nhận đăng ký từ các thí sinh. Trong giai đoạn này, Nhà tổ chức cuộc thi sẽ vừa quảng bá cuộc thi vừa nhận thông tin các thí sinh tham gia.", "challengeRegister": "Thí Sinh Phải Đăng Ký Không Quá: *", "challengeRegisterTip": "Thông thường, Ngày Hết Hạn Đăng Ký sẽ sau Ngày Bắt Đầu khoảng từ 2 đến 4 tuần. Khi hết hạn đăng ký, cuộc thi sẽ chính thức diễn ra. Trong giai đoạn này, bạn vẫn có thể chấp nhận thêm các đăng ký mới.", - "challengeSubmit": "Và Thí Sinh Phải Gửi Kết Quả Không Quá: *", + "challengeSubmit": "Và Thí Sinh Phải Gửi Sản Phẩm Cuối Trước Ngày: *", "challengeSubmitTip": "Thời gian diễn ra của cuộc thi bình thường sẽ không quá 8 tuần. Ngày Hết Hạn Nộp Bài được xem là ngày kết thúc của cuộc thi. Mọi thí sinh phải nộp sản phẩm trước hạn nộp bài.", "userName": "Tên truy cập/Địa chỉ Email: *", "userNameEX": "Tên truy cập / example@vietnamworks.com", @@ -750,5 +750,8 @@ "finalApp": "Final App", "mailSubject": "Tiêu đề:", "mailTo": "Gửi tới:", - "mailSend": "Gửi Email" + "mailSend": "Gửi Email", + "challengeIdeaTips": "Đây là ngày kết thúc giai đoạn Ý Tưởng của cuộc thi và thí sinh phải đề xuất ý tưởng trước ngày này. Nhà Tổ Chức sẽ phân loại, đánh giá và sắp xếp các ý tưởng. Các thí sinh với ý tưởng xuất sắc sẽ được lựa chọn để vào tiếp vòng trong.", + "challengeUIUXTips":"Đây là ngày kết thúc giai đoạn UI/UX của cuộc thi và thí sinh phải nộp UI/UX mockup và/hay kết quả Kiểm Tra Khả Dụng (Usability Test) nếu được yêu cầu trước ngày này. Nhà Tổ Chức sẽ chịu trách nhiệm đánh giá UI/UX mockup và sẽ lựa chon các thí sinh với UI/UX mockup xuất sắc vào vòng tiếp theo.", + "challengePrototypeTips":"Đây là ngày kết thúc giai đoạn Prototype của cuộc thi và thí sinh phải nộp Prototype trước ngày này. Nhà Tổ Chức sẽ chịu trách nhiệm đánh giá Prototype và sẽ lựa chon các thí sinh với Prototype xuất sắc vào vòng tiếp theo." } diff --git a/src/main/webapp/assets/sass/contest-detail.sass b/src/main/webapp/assets/sass/contest-detail.sass index 9f4ed9eff..aca2e84a9 100644 --- a/src/main/webapp/assets/sass/contest-detail.sass +++ b/src/main/webapp/assets/sass/contest-detail.sass @@ -341,6 +341,7 @@ height: 20px border-right: 1px dotted #ccc padding-bottom: 0 + float: left span.date-right text-align: right font-weight: 300 @@ -349,7 +350,7 @@ clear: none float: right font-size: 12px - padding: 10px 2px 0 0 + padding: 0 2px 0 0 span.date-left text-align: left font-weight: 300 @@ -358,51 +359,76 @@ clear: none float: left font-size: 12px - padding: 10px 0 0 2px + padding: 0 + span.line-dot + height: 20px + width: 100% + display: inline-block + clear: both + float: left .phase-box.phase-registration color: #1ea185 padding-bottom: 0 span.line-color background-color: #1ea185 - span.date-right + span.line-dot border-right: 1px dotted #1ea185 - span.date-left border-left: 1px dotted #1ea185 + span.date-left + i + margin-left: -5px + span.date-right + margin-right: -7px .phase-box.phase-idea color: #9bba5c padding-bottom: 0 span.line-color background-color: #9bba5c span.date-right + i + margin-right: -7px + span.line-dot border-right: 1px dotted #9bba5c .phase-box.phase-uiux color: #f29b27 padding-bottom: 0 span.line-color background-color: #f29b27 - span.date-right + span.line-dot border-right: 1px dotted #f29b27 + span.date-right + i + margin-right: -7px .phase-box.phase-prototype color: #bd392f padding-bottom: 0 span.line-color background-color: #bd392f - span.date-right + span.line-dot border-right: 1px dotted #bd392f + span.date-right + i + margin-right: -7px .phase-box.phase-in-progress color: #257abb padding-bottom: 0 span.line-color background-color: #257abb - span.date-right + span.line-dot border-right: 1px dotted #257abb + span.date-right + i + margin-right: -7px .phase-box.phase-final-app color: #257abb padding-bottom: 0 span.line-color background-color: #257abb - span.date-right + span.line-dot border-right: 1px dotted #257abb + span.date-right + i + margin-right: -7px .phase-box.two-cols width: 50%