├── __mocks__
├── fileMock.js
├── lottie-web.js
└── gatsby.js
├── config
├── loaderShim.js
├── jest-preprocess.js
├── jest.setup.js
└── helpers.js
├── src
├── images
│ ├── favicon.png
│ ├── socialBanner1200x628.png
│ ├── star_solid.svg
│ ├── plus.svg
│ ├── arrow.svg
│ ├── caret-left.svg
│ └── star.svg
├── pages
│ ├── 404.js
│ ├── setup.js
│ ├── index.js
│ ├── success.js
│ └── about.js
├── components
│ ├── shared
│ │ ├── hooks
│ │ │ ├── useBackgroundColorUpdater.js
│ │ │ ├── useQuestionnaire.js
│ │ │ ├── usePopup.js
│ │ │ ├── useOuterClick.js
│ │ │ ├── useForm.js
│ │ │ └── useCheckbox.js
│ │ ├── styles.js
│ │ ├── providers
│ │ │ └── questionnaire
│ │ │ │ ├── index.js
│ │ │ │ └── reducers.js
│ │ ├── transition.js
│ │ ├── animations
│ │ │ └── rocket-link.js
│ │ ├── form-components
│ │ │ ├── checkbox.js
│ │ │ └── index.js
│ │ ├── layout.js
│ │ ├── modal.js
│ │ ├── layout.css
│ │ ├── header.js
│ │ └── shared-styles.js
│ ├── questionnaire
│ │ ├── __tests__
│ │ │ ├── index.test.js
│ │ │ └── steps
│ │ │ │ ├── health_plan.test.js
│ │ │ │ ├── bonus_moonshot.test.js
│ │ │ │ ├── bonus_community.test.js
│ │ │ │ ├── occupation_plan.test.js
│ │ │ │ ├── interests_plan.test.js
│ │ │ │ ├── relationships_plan.test.js
│ │ │ │ ├── about.test.js
│ │ │ │ ├── health.test.js
│ │ │ │ ├── occupation.test.js
│ │ │ │ ├── relationships.test.js
│ │ │ │ ├── interests.tests.js
│ │ │ │ └── final.test.js
│ │ ├── steps
│ │ │ ├── health
│ │ │ │ ├── index.js
│ │ │ │ └── health-plan.js
│ │ │ ├── relationships
│ │ │ │ ├── index.js
│ │ │ │ └── relationships-plan.js
│ │ │ ├── hobbies
│ │ │ │ ├── personal-interests-plan.js
│ │ │ │ └── index.js
│ │ │ ├── shared
│ │ │ │ ├── intro.js
│ │ │ │ ├── index.js
│ │ │ │ └── add-new-checkbox-item.js
│ │ │ ├── moonshot
│ │ │ │ └── index.js
│ │ │ ├── community
│ │ │ │ └── index.js
│ │ │ ├── occupation
│ │ │ │ ├── occupation-plan.js
│ │ │ │ ├── reducers.js
│ │ │ │ └── index.js
│ │ │ ├── about
│ │ │ │ └── index.js
│ │ │ └── final
│ │ │ │ └── index.js
│ │ ├── questionnaire-steps.js
│ │ └── index.js
│ └── image.js
├── models
│ └── questionnaire.js
└── utils.js
├── .prettierrc
├── .babelrc
├── gatsby-node.js
├── README.md
├── jest.config.js
├── LICENSE
├── .gitignore
├── gatsby-config.js
└── package.json
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub'
2 |
--------------------------------------------------------------------------------
/config/loaderShim.js:
--------------------------------------------------------------------------------
1 | global.___loader = {
2 | enqueue: jest.fn(),
3 | }
4 |
--------------------------------------------------------------------------------
/src/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timc1/time-capsule/HEAD/src/images/favicon.png
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/src/images/socialBanner1200x628.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timc1/time-capsule/HEAD/src/images/socialBanner1200x628.png
--------------------------------------------------------------------------------
/__mocks__/lottie-web.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 |
3 | module.exports = {
4 | loadAnimation: jest.fn(),
5 | destroy: jest.fn(),
6 | }
7 |
--------------------------------------------------------------------------------
/config/jest-preprocess.js:
--------------------------------------------------------------------------------
1 | const babelOptions = {
2 | presets: ['babel-preset-gatsby'],
3 | }
4 |
5 | module.exports = require('babel-jest').createTransformer(babelOptions)
6 |
--------------------------------------------------------------------------------
/config/jest.setup.js:
--------------------------------------------------------------------------------
1 | // add some helpful assertions
2 | import 'jest-dom/extend-expect'
3 |
4 | // this is basically: afterEach(cleanup)
5 | import 'react-testing-library/cleanup-after-each'
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | presets: [
3 | [
4 | "babel-preset-gatsby",
5 | {
6 | targets: {
7 | browsers: [">0.25%", "not dead"],
8 | },
9 | },
10 | ],
11 | ],
12 | plugins: [
13 | '@babel/plugin-proposal-optional-chaining',
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Layout from '../components/shared/layout'
3 |
4 | const NotFoundPage = () => (
5 | You just hit a route that doesn't exist... the sadness.NOT FOUND
7 |
An animated, fun, and accessible web app to send your future (365 days) self a letter.
8 | 9 | ## About 10 | 11 | With the New Year coming up, write your future self a letter on your goals, interests, and how you plan to accomplish things you'd like to get done. 12 | 13 | 365 days from today, you'll receive your own message in your inbox! 14 | 15 | Hope you accomplish your goals! 16 | 17 | ## 🚀 Set up 18 | 19 | ``` 20 | npm install 21 | ``` 22 | 23 | ``` 24 | npm start 25 | ``` 26 | 27 | ``` 28 | npm run test 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.jsx?$': '{text}
11 | 16 |(or click Esc)
103 |User: ${user.name}
`, 122 | partner: true, 123 | } 124 | : { 125 | from: `This Next Year <${user.name}>`, 126 | subject: `New Submission! <${user.name}>`, 127 | text: ``, 128 | html: `User: ${user.name}
`, 129 | partner: true, 130 | } 131 | 132 | fetch(url, { 133 | method: 'POST', // *GET, POST, PUT, DELETE, etc. 134 | body: JSON.stringify(body), // body data type must match "Content-Type" header 135 | mode: 'cors', // no-cors, cors, *same-origin 136 | cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached 137 | headers: { 138 | 'Content-Type': 'application/json; charset=utf-8', 139 | }, 140 | redirect: 'follow', // manual, *follow, error 141 | referrer: 'no-referrer', // no-referrer, *client 142 | }) 143 | .then(response => response.json()) 144 | .catch(error => ({ error: 'connection error' })) 145 | } 146 | 147 | const emojis = ['🤪', '😁', '😎', '😜', '🤗', '🙃'] 148 | const greetings = ['nice.', 'looks good.', 'sounds great.'] 149 | export { 150 | randomNum, 151 | randomEmoji, 152 | randomGreeting, 153 | debounce, 154 | deepClone, 155 | http, 156 | noop, 157 | API_URL, 158 | camelToUnderscore, 159 | sendEmail, 160 | } 161 | -------------------------------------------------------------------------------- /src/components/shared/layout.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'NBInternational'; 3 | font-display: auto; 4 | src: url('/assets/fonts/NBInternational_Regular.ttf'); 5 | font-weight: 100 500; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: 'NBInternational'; 11 | font-display: auto; 12 | src: url('/assets/fonts/NBInternational_Bold.ttf'); 13 | font-weight: 600 900; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: 'DomaineDisplayNarrow'; 19 | font-display: auto; 20 | src: url('/assets/fonts/DomaineDisplayNarrow-Semibold.otf'); 21 | font-weight: 100 900; 22 | font-style: normal; 23 | } 24 | 25 | :root { 26 | --ff-serif: "DomaineDisplayNarrow", "Georgia", "Times", "Times New Roman"; 27 | --ff-sanserif: "NBInternational", "Helvetica Neue", Helvetica, Arial, sans-serif; 28 | --fontbold: 800; 29 | --fontregular: 400; 30 | 31 | --fontxs: .75rem; 32 | --fontsm: .85rem; 33 | --fontmd: 1rem; 34 | --fontidk: 1.25rem; 35 | --fontlg: 3rem; 36 | --fontxl: 4.5rem; 37 | --baselineheight: 1.5rem; 38 | 39 | --black: #000; 40 | --black1: rgba(15, 13, 32, 0.99); 41 | --black2: rgba(0,0,0,.95); 42 | --white: #fff; 43 | --white1: rgba(255,255,255,.95); 44 | --white2: rgba(246, 250, 253, 0.9); 45 | --white3: rgba(255,255,255,.3); 46 | 47 | --gray: #F6FAFD; 48 | --gray1: #c6c9cc; 49 | --gray2: #606467; 50 | --gray3: #272635; 51 | --blue: #0b1cfd; 52 | --blue1: #2f5cff; 53 | --error: #ff6063; 54 | 55 | --c1: rgba(84, 247, 211, 0.3); 56 | --c2: rgba(126, 198, 255, 0.3); 57 | --c3: rgba(45, 184, 255, 0.3); 58 | --c4: rgba(255, 51, 51, 0.3); 59 | --c5: rgba(255, 123, 123, 0.3); 60 | --c6: rgba(11, 28, 253, 0.3); 61 | 62 | --baseborderpadding: 25px; 63 | --baseborderradius: 2px; 64 | 65 | --baseboxshadow: 0 1px 3px rgba(188, 193, 217, .12), 0 5px 12px rgba(188, 193, 217, .25); 66 | --boxshadow2: 0 13px 27px -5px rgba(50,50,93,.22), 0 8px 16px -8px rgba(0,0,0,.2); 67 | 68 | --cubic: cubic-bezier(0.42, 0, 0.59, 1.1); 69 | --cubic2: cubic-bezier(0.65,-0.22, 0.43, 0.72); 70 | } 71 | 72 | @media(max-width: 568px) { 73 | :root { 74 | --fontxl: 11vw; 75 | --fontlg: 9vw; 76 | --fontidk: 5vw; 77 | } 78 | } 79 | 80 | ::selection { 81 | background: #3C91F0; /* WebKit/Blink Browsers */ 82 | color: #fff; 83 | } 84 | ::-moz-selection { 85 | background: #3C91F0;/* Gecko Browsers */ 86 | color: #fff; 87 | } 88 | 89 | * { 90 | -webkit-text-size-adjust: 100%; 91 | -webkit-font-smoothing: antialiased; 92 | -moz-osx-font-smoothing: grayscale; 93 | text-rendering: optimizeLegibility; 94 | -webkit-font-feature-settings: "pnum"; 95 | font-feature-settings: "pnum"; 96 | font-variant-numeric: proportional-nums; 97 | font-family: var(--ff-sanserif); 98 | font-size: var(--fontmd); 99 | box-sizing: border-box; 100 | } 101 | 102 | html { 103 | font-family: sans-serif; 104 | -ms-text-size-adjust: 100%; 105 | -webkit-text-size-adjust: 100%; 106 | height: 100%; 107 | position: relative; 108 | } 109 | 110 | @media(max-width: 568px) { 111 | html { 112 | min-height: 650px; 113 | height: 100%; 114 | } 115 | } 116 | 117 | body { 118 | margin: 0; 119 | transition: background .15s ease-in; 120 | } 121 | 122 | body[data-url="/"] { 123 | background: var(--white); 124 | } 125 | 126 | body[data-url="/setup"] { 127 | background: var(--white); 128 | } 129 | 130 | a, button, input, select, textarea, label { 131 | -webkit-tap-highlight-color: transparent; 132 | } 133 | 134 | a { 135 | text-decoration: none; 136 | } 137 | 138 | ul { 139 | margin: 0; 140 | padding: 0; 141 | } 142 | 143 | li { 144 | list-style: none; 145 | } 146 | 147 | input:-webkit-autofill { 148 | -webkit-text-fill-color: var(--blue1); 149 | transition: background-color 100000000s; 150 | } 151 | 152 | body { 153 | margin: 0; 154 | padding: 0; 155 | } 156 | 157 | .hidden { 158 | opacity: 0; 159 | display: none; 160 | } 161 | 162 | .wizard { 163 | position: absolute; 164 | width: 100%; 165 | transition-property: opacity, transform; 166 | } 167 | 168 | .wizard-horizontal-right-enter-done, 169 | .wizard-horizontal-left-enter-done { 170 | opacity: 1; 171 | transform: translateX(0); 172 | transition-duration: .2s; 173 | transition-timing-function: var(--cubic); 174 | } 175 | 176 | .wizard-horizontal-left-enter-active, 177 | .wizard-horizontal-right-enter-active 178 | { 179 | opacity: 0; 180 | } 181 | .wizard-horizontal-left-enter-active { 182 | transform: translateX(100px); 183 | } 184 | .wizard-horizontal-right-enter-active { 185 | transform: translateX(-100px); 186 | } 187 | 188 | .wizard-horizontal-left-exit-active, 189 | .wizard-horizontal-right-exit-active 190 | { 191 | opacity: 0; 192 | transition-duration: .2s; 193 | transition-timing-function: var(--cubic); 194 | } 195 | .wizard-horizontal-left-exit-active { 196 | transform: translateX(-100px); 197 | } 198 | .wizard-horizontal-right-exit-active { 199 | transform: translateX(100px); 200 | } 201 | 202 | .wizard-horizontal-right-exit-done { 203 | transform: translateX(-100px); 204 | } 205 | .wizard-horizontal-left-exit-done { 206 | transform: translateX(100px); 207 | } 208 | 209 | -------------------------------------------------------------------------------- /src/components/questionnaire/steps/shared/add-new-checkbox-item.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from 'react' 2 | import { Form, Label, Input } from '../../../shared/form-components/index' 3 | 4 | import useForm from '../../../shared/hooks/useForm' 5 | import useQuestionnaire from '../../../shared/hooks/useQuestionnaire' 6 | 7 | import { SmallModalContainer } from '../shared/index' 8 | 9 | import { AnimatedButton, scaleIn } from '../../../shared/styles' 10 | import styled from '@emotion/styled' 11 | import plus from '../../../../images/plus.svg' 12 | 13 | export default React.memo( 14 | ({ toggleModal, sectionToUpdate = '', title = '', placeholder = '' }) => { 15 | const initialFocusRef = useRef() 16 | const { context } = useQuestionnaire() 17 | const { getFormProps, getInputStateAndProps, state } = useForm({ 18 | initialValues: { 19 | [sectionToUpdate]: '', 20 | }, 21 | }) 22 | 23 | useEffect(() => { 24 | initialFocusRef.current.focus() 25 | }, []) 26 | 27 | const val = state[sectionToUpdate].trim() 28 | return ( 29 |{meta.sectionTitle}
113 |36 | This Next Year is a project that enables people to send their future 37 | selves a letter and receive it one year later. 38 |
39 |40 | It is part of our initiative to design & develop resources to empower 41 | others to create. 42 |
43 |76 | Download brand assets{' '} 77 | 78 | here 79 | 80 |
81 |
84 | View code on{' '}
85 |
98 | We are always open to collaborating and building products with other
99 | creators. Shoot us an email{' '}
100 |
107 | Data collected on this site is used solely for the email sent to the 108 | email provided. Once the email is sent, it and its correlated data 109 | will be deleted.{' '} 110 | 111 | ✌️ 112 | 113 |
114 |