The ID of the experiment to save data for. This ID is provided when an experiment is created on DataPipe.
55 |
56 |
57 |
data
58 |
The text-based data to save, sent as a string.
59 |
60 |
61 |
filename
62 |
The name of the file to create. This filename must be unique. If the file already exists the request will fail.
63 |
64 |
65 |
66 |
67 |
68 |
69 | Save base64-encoded data
70 |
71 |
72 | POST /api/base64
73 |
74 |
75 | Save a base64-encoded file to the OSF. The file will be decoded before being posted to the OSF.
76 |
77 |
78 |
79 |
80 |
Parameter
81 |
Description
82 |
83 |
84 |
85 |
86 |
experimentID
87 |
The ID of the experiment to save data for. This ID is provided when an experiment is created on DataPipe.
88 |
89 |
90 |
data
91 |
The base64-encoded data to save, sent as a string.
92 |
93 |
94 |
filename
95 |
The name of the file to create. This filename must be unique. If the file already exists the request will fail.
96 |
97 |
98 |
99 |
100 |
101 |
102 | Get condition assignment
103 |
104 |
105 | POST /api/condition
106 |
107 |
108 | Get the next condition assignment in an experiment. The condition is a numerical value from 0 to n-1, where n
109 | is the number of conditions in the experiment. Condition assignment is sequential, so the first request will
110 | return 0, the second request will return 1, and so on.
111 |
112 |
113 |
114 |
115 |
Parameter
116 |
Description
117 |
118 |
119 |
120 |
121 |
experimentID
122 |
The ID of the experiment. This ID is provided when an experiment is created on DataPipe.
123 |
124 |
125 |
126 |
127 | On a successful request, the JSON response will contain a condition parameter with the condition assignment.
128 |
129 |
130 |
131 |
132 | API Responses
133 |
134 | Responses from the API are JSON. On a successful request the response will contain a message{' '}
135 | parameter will the value Success. When an error occurs, the responses will contain one of the error{' '}
136 | and message parameter sets shown below.
137 |
138 |
139 |
140 |
Error
141 |
Message
142 |
143 |
144 |
145 |
146 |
MISSING_PARAMETER
147 |
One or more required parameters are missing.
148 |
149 |
150 |
DATA_COLLECTION_NOT_ACTIVE
151 |
Data collection is not active for this experiment
152 |
153 |
154 |
BASE64DATA_COLLECTION_NOT_ACTIVE
155 |
Base64 data collection is not active for this experiment
156 |
157 |
158 |
CONDITION_ASSIGNMENT_NOT_ACTIVE
159 |
Condition assignment is not active for this experiment
160 |
161 |
162 |
EXPERIMENT_NOT_FOUND
163 |
The experiment ID does not match an experiment
164 |
165 |
166 |
INVALID_OWNER
167 |
The owner ID of this experiment does not match a valid user
168 |
169 |
170 |
INVALID_OSF_TOKEN
171 |
The OSF token for this experiment is not valid
172 |
173 |
174 |
INVALID_BASE64_DATA
175 |
The data are not valid base64 data
176 |
177 |
178 |
INVALID_DATA
179 |
180 | The data are not valid according to the validation parameters set
181 | for this experiment.
182 |
183 |
184 |
185 |
SESSION_LIMIT_REACHED
186 |
The session limit for this experiment has been reached
187 |
188 |
189 |
UNKNOWN_ERROR_GETTING_CONDITION
190 |
191 | An unknown error occurred while getting the condition for this
192 | experiment
193 |
194 |
195 |
196 |
OSF_FILE_EXISTS
197 |
198 | The OSF file already exists. File names must be unique.
199 |
200 |
201 |
202 |
OSF_UPLOAD_ERROR
203 |
An error occurred while uploading the data to OSF
204 |
205 |
206 |
207 |
208 |
209 | );
210 | }
211 |
--------------------------------------------------------------------------------
/pages/contact.js:
--------------------------------------------------------------------------------
1 | import { Stack, Heading, Text, Button, Link } from "@chakra-ui/react";
2 |
3 | export default function Contact() {
4 | return (
5 |
6 |
7 | Contact Us
8 |
9 |
10 | DataPipe is a free service provided by the developers of jsPsych. We do
11 | not have a dedicated support team, but we do our best to respond to
12 | questions and issues.
13 |
14 |
15 | We ask that if you have a question or issue, you first check the{" "}
16 |
17 | GitHub repository issues
18 | {" "}
19 | to see if your question has already been answered. If not, we encourage
20 | you to post a new issue there.
21 |
22 |
23 | If you need to contact us directly, you can email Josh de Leeuw at
24 | jdeleeuw@vassar.edu.
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/pages/faq.js:
--------------------------------------------------------------------------------
1 | import {
2 | Stack,
3 | Heading,
4 | Accordion,
5 | AccordionItem,
6 | AccordionButton,
7 | AccordionPanel,
8 | AccordionIcon,
9 | Text,
10 | OrderedList,
11 | ListItem,
12 | Link,
13 | } from "@chakra-ui/react";
14 | import NextLink from "next/link";
15 |
16 | export default function FAQ() {
17 | return (
18 |
19 |
20 | FAQ
21 |
22 |
23 |
24 |
25 | DataPipe serves as a connection between an experiment and the Open
26 | Science Framework. To use DataPipe, you will need to use a webhost
27 | to get your experiment online (e.g., GitHub Pages) and then add some
28 | code to your experiment to send data to DataPipe. You will also need
29 | to have an OSF account to store the data and create an authorization
30 | token on the OSF to allow DataPipe to write data to your OSF
31 | account. Our{" "}
32 |
33 | getting started guide
34 | {" "}
35 | has more information about how to use DataPipe.
36 |
37 |
38 |
39 |
40 | No, you will need to use a different service to make the experiment
41 | available online. The benefit of using this service is that you do
42 | not need to configure any of the backend/server components of an
43 | experiment, so you can use a provider like GitHub Pages to host the
44 | experiment for free.
45 |
46 |
47 |
48 | This guide on GitHub Pages
49 | {" "}
50 | describes how to set up a free website using their service. In the
51 | guide, select "project site" and "start from
52 | scratch" and follow the guide to get an experiment hosted
53 | quickly.
54 |
55 |
56 |
57 |
58 | Not directly. DataPipe helps you store your data on the Open Science
59 | Framework. When you use DataPipe, the data is routed through our
60 | service to (optionally) perform validation and then we send it to
61 | the OSF. DataPipe does not store a copy of the data.
62 |
63 |
64 |
65 | DataPipe is free to use.
66 |
67 |
68 |
69 | The expensive parts of hosting an experiment are providing storage
70 | and bandwidth for the experiment files and data. Fortunately there
71 | are providers who are willing to do both of these things for free.
72 | GitHub (and others) will host a website for free and the Open
73 | Science Framework will store data for free. Unfortunately these
74 | providers are not directly connected to each other, so that is what
75 | we are trying to solve. DataPipe is a very lightweight (i.e., cheap)
76 | service that makes it easy to link a hosting provider with a data
77 | storage provider.
78 |
79 |
80 |
81 |
82 | We host DataPipe using Google Firebase, so the cost of DataPipe
83 | depends on how much usage it gets. Currently our resource
84 | consumption is less than $1 per month. Once we have been up and
85 | running for a while we will post more information about how much it
86 | costs to run the service. We have funding reserves in the{" "}
87 |
91 | Open Collective account for jsPsych development
92 | {" "}
93 | to sustain this serivce. Our goal is to provide transparent
94 | information about our costs and our available funds to run the
95 | service so you can determine whether we are likely to be able to
96 | keep the service running. We are grateful for donations to help keep
97 | the service running. If you{" "}
98 |
102 | donate a few dollars to our account
103 | {" "}
104 | you should cover the lifetime cost of providing this service to you.
105 |
106 |
107 |
108 |
109 | The data that you send to DataPipe are not stored anywhere on our
110 | servers and we do not log any information about the data when it is
111 | sent. If your OSF component that receives the data is private, then
112 | you have full control over who can see the data. If your OSF
113 | component is public, then anyone can see the data.
114 |
115 |
116 |
117 |
118 | There are a few risks that you should be aware of before using
119 | DataPipe.
120 |
121 |
122 |
123 | In order to use this service you must provide us with an OSF
124 | authorization token so that we can write data to your OSF account
125 | on your behalf. This key enables full write access, so if we
126 | suffer a data breach it would be possible for someone who got
127 | access to the token to make malicious changes to your OSF account.
128 | To mitigate this risk, you should create an OSF token that is just
129 | for this service so that you can revoke authorization when you are
130 | done using the service. The strongest security would be to use an
131 | active token only when you need to collect data through this
132 | service.
133 |
134 |
135 | This service does allow a technically savvy user to potentially
136 | write fake data to your OSF project. This is almost always a risk
137 | with online experiments because the data are usually recorded on
138 | the participant's computer before being sent to the server.
139 | It is possible for a malicious user to change or create the data
140 | before sending it to the service. It is also possible that a user
141 | could spam data to your OSF account or could send files that are
142 | not actually experiment data. We provide tools to mitigate these
143 | risks by allowing you to specify validation rules for the data
144 | that is sent and to rate limit the amount of data you are
145 | receiving.
146 |
147 |
148 | This service is not a commercial venture with a dedicated user
149 | support team. If something goes wrong, we may not be able to
150 | respond quickly. However, the{" "}
151 |
152 | code that runs this service is open source
153 | {" "}
154 | and thoroughly tested. The service is hosted using the Google
155 | Cloud, so we get the benefit of Google's infrastructure to
156 | make sure the service is secure and keeps running.
157 |
158 |
159 |
160 |
161 |
162 | DataPipe has an optional feature that will validate any incoming
163 | data before sending it to the OSF. Currently, this feature supports
164 | checking whether an incoming file is valid JSON or CSV data (meaning
165 | that it has the correct format) and it allows you to specify a set
166 | of columns/fields that the data must contain. For CSV data, the
167 | validation simply checks if all of the required columns are in the
168 | header row. For JSON data, the validation checks if all of the
169 | required fields are present in at least one object in the data. For
170 | example, if the data are an array of trials (as jsPsych generates),
171 | then this validation will generate a list of all of the unique
172 | fields that are present in any of the trials and then check if all
173 | of the required fields are present. This is equivalent to converting
174 | the data to CSV format and then checking if all of the required
175 | columns are present.
176 |
177 |
178 | If an invalid data file is sent, it will be rejected and not sent to
179 | the OSF. There is no way to recover this data. This feature is not
180 | designed to catch errors in legitimate data files. It is designed to
181 | prevent malicious users from sending non-data files to your OSF
182 | project.
183 |
184 |
185 |
186 |
187 | DataPipe has an optional feature that will collect data as base 64
188 | encoded strings. This is useful if you want to collect data that is
189 | not text-based, like JSON or CSV. This feature is designed to
190 | collect a single file of data at a time. For example, if you are
191 | running an experiment where the participant will record several
192 | audio files, you could use this feature to send each file to the OSF
193 | as it is recorded. When DataPipe gets a base 64 encoded file, it
194 | will decode it and then send it to the OSF as a file.
195 |
196 |
197 | Note that validating base 64 encoded data is not currently
198 | supported, so enabling this feature does create additional risk. We
199 | recommend minimizing this risk by enabling the feature only when you
200 | are actively collecting data and disabling it when you are not.
201 |
202 |
203 |
204 |
205 | When you enable condition assignment, you can call an API endpoint
206 | to get the next condition for a participant. DataPipe will send you
207 | a number between 0 and n-1, where n is the number of conditions you
208 | set in your experiment. If you have an experimental design with
209 | multiple factors, set the number of conditions to the number of
210 | unique cells in your design, and then use the condition number to
211 | determine the appropriate level of each factor. DataPipe generates
212 | condition numbers sequentially, so if you have 3 conditions, the
213 | first participant will get condition 0, the second participant will
214 | get condition 1, the third participant will get condition 2, and
215 | then the cycle will repeat.
216 |
217 |
218 |
219 |
220 | );
221 | }
222 |
223 | function FAQItem({ question, children }) {
224 | return (
225 |
226 |
227 |
228 | {question}
229 |
230 |
231 |
232 |
233 | {children}
234 |
235 |
236 | );
237 | }
238 |
--------------------------------------------------------------------------------
/pages/getting-started.js:
--------------------------------------------------------------------------------
1 | import {
2 | Stack,
3 | Heading,
4 | Text,
5 | Button,
6 | Link,
7 | OrderedList,
8 | ListItem,
9 | } from "@chakra-ui/react";
10 |
11 | export default function GettingStarted() {
12 | return (
13 |
14 |
15 | Getting Started
16 |
17 |
18 | You can use DataPipe with any online experiment. You can even use it
19 | with a laboratory experiment, as long as you have an internet
20 | connection. This guide will walk you through the steps for a typical
21 | online experiment using tools that are widely available and free.
22 |
23 |
24 | Create an OSF project for your experiment
25 |
26 |
27 | The first step is to create an OSF project for your experiment. You can
28 | create an OSF project at{" "}
29 |
30 | https://osf.io
31 |
32 | . You will need to create an account if you do not already have one.
33 | Once you have created an account, click the Create Project button to
34 | create a new project. You can name your project whatever you want.
35 |
36 |
37 | Link your OSF account to DataPipe
38 |
39 |
40 | In order for DataPipe to have permission to send files to your OSF
41 | account, you need to create an authorization token on the OSF and add
42 | the token to your DataPipe account. To create an authorization token, go
43 | to your OSF account settings by clicking your name in the top right
44 | corner of the screen and selecting Settings. Then click the Personal
45 | Access Tokens tab. Click the Create Token button. Give the token a name
46 | (we recommend a name that is specific to DataPipe so that you can easily
47 | disable the token when you are done using DataPipe) and select
48 | "osf.full_write" as the scope. Click the Create Token button
49 | to finish creating the token. You will be shown the token value. Copy
50 | the token value.
51 |
52 |
53 | On DataPipe, click the Account button in the top right corner and select
54 | Settings. Click the Set OSF Token button and paste the token value into
55 | the box. Click Change Token to finish. You should see the icon become a
56 | green checkmark to indicate that you have a valid token.
57 |
58 |
59 | Create a DataPipe experiment
60 |
61 |
62 | The next step is to create an experiment on DataPipe. Click the New
63 | Experiment button in the top right corner. Give your experiment a name
64 | and enter the OSF project ID. This ID is part of the URL of the OSF
65 | project. For example, if the URL of your OSF project is
66 | https://osf.io/abcde/, then the project ID is abcde.
67 |
68 |
69 | When you create an experiment, DataPipe will automatically create a new
70 | Data component on the OSF project. The Data component is where DataPipe
71 | will store the data files that it sends to the OSF project. Enter the
72 | name you would like to use for the Data component.
73 |
74 |
75 | Click the create experiment button to finish. You will be sent to the
76 | experiment dashboard where you can edit the experiment settings.
77 |
78 |
79 | Configure the experiment
80 |
81 |
82 | There are three optional features that you can enable for your
83 | experiment via the experiment dashboard.
84 |
85 |
86 | Condition assignment will
87 | allow you to request the next sequential condition number from DataPipe.
88 | For example, if you have 4 conditions in the experiment, DataPipe will
89 | respond to the first request with 0, the next request with 1, then 2,
90 | then 3, and then cycle back to 0.
91 |
92 |
93 | Data validation will check
94 | the data as it is sent to DataPipe. If the data are invalid, then
95 | DataPipe will not send the data to the OSF. The basic data validation
96 | features are to check if the data file is a valid JSON or CSV file. Once
97 | you have created the experiment, you can also specify a list of required
98 | fields that the JSON or CSV must have in order to be considered valid.
99 | This is a useful feature to enable because it limits the potential for
100 | malicious use of DataPipe. One risk of using DataPipe is that it creates
101 | an open path to create files in your OSF project. A malicious and
102 | technically savvy user could potentially create spam data and send the
103 | files to your OSF account. Turning on validation makes it harder to do
104 | this.
105 |
106 |
107 | The session limit will cap
108 | the number of data files that can be sent to your OSF project. This is
109 | another way to limit the potential for malicious use. If you set the
110 | session limit to 100, then DataPipe will only send the first 100 data
111 | files that it receives. You can adjust the session limit later if you
112 | need to increase it.
113 |
114 |
115 | Add code to your experiment to send data to DataPipe
116 |
117 |
118 | In order to send data to DataPipe, you need to add code to your
119 | experiment to communicate with DataPipe. If you are using jsPsych,
120 | then you can use the jsPsychPipe plugin. If you are not using jsPsych, then you can use the DataPipe API
121 | directly via fetch requests.
122 |
123 |
124 | After creating an experiment in the previous step, you will be sent to
125 | the experiment page. You can also get to the experiment page by clicking
126 | My Experiments in the top menu and selecting the experiment you want to
127 | view. On this page, there are code snippets for sending data to
128 | DataPipe. Select the code snippet for the language that you are using in
129 | your experiment and follow the instructions provided on the dashboard to add the code to your experiment.
130 |
131 |
132 |
133 | Publish your experiment
134 |
135 |
136 | The next step is to publish your experiment online so that participants
137 | can view it. You can use any tool that allows you to publish a web page,
138 | such as university web hosting, GitHub Pages, or Netlify. We will
139 | describe how to use GitHub Pages, since it is free, accessible, and
140 | relatively easy to use. This guide will assume no familiarity with
141 | GitHub or git version control. We will describe the easiest way to get
142 | started for someone with no experience using GitHub, but if you are
143 | already familiar with GitHub, the approach we take here is probably not
144 | the best way to do things and you should feel free to follow your own
145 | preferred workflow.
146 |
147 |
148 | First, create a GitHub account at{" "}
149 |
150 | https://github.com
151 | {" "}
152 | if you do not already have one. Then go to{" "}
153 |
154 | https://github.com/new
155 | {" "}
156 | to create a new repository. You can name it whatever you want, but the
157 | name that you give it will become part of the URL that you use to access
158 | your experiment. Therefore, you may want to avoid names that reveal
159 | information that you want to keep hidden from the participants. Check
160 | the box to add a README file. The rest of the settings can be left at
161 | their default values. Click the "Create repository" button to
162 | create the repository.
163 |
164 |
165 | Next, configure the repository to share its content as a webpage. Go to
166 | the Settings tab of your repository. Select the Pages menu item on the
167 | left side. For Source, leave it as deploy from a branch. Under branch
168 | select main as the source. Click the save button to finish this step.
169 |
170 |
171 | Now add the experiment files to the repository. In your GitHub
172 | repository, click the Add Files button near the top of the screen and
173 | select Upload Files. Drag and drop your experiment files into the upload
174 | box. You can also click the upload box to select the files from your
175 | computer. Once you have uploaded all of your experiment files, click the
176 | Commit Changes button.
177 |
178 |
179 | That's it! Your experiment is now published on the web. You can
180 | view it by going to https://[your username].github.io/[your repository
181 | name]. If your HTML file is not named index.html, then you need to add
182 | the name of the HTML file to the end of the URL. For example, if your
183 | HTML file is called experiment.html, then the URL would be https://[your
184 | username].github.io/[your repository name]/experiment.html. It may take
185 | a few minutes for the uploaded files to be available as a website.
186 |
187 |
188 | Activate your experiment
189 |
190 |
191 | The final step is to activate your experiment. On the experiment
192 | dashboard, you can activate three different features of DataPipe for
193 | each experiment.
194 |
195 |
196 |
197 | Enable data collection will
198 | activate the standard data collection feature. This enables sending text
199 | files (e.g., JSON or CSV) to your OSF project.
200 |
201 |
202 |
203 | Enable base64 data collection
204 | {" "}
205 | activates base64-based data collection. Base 64 is a way to encode files
206 | as strings, and can be used for collecting data like audio recordings,
207 | video recordings, or images.
208 |
209 |
210 | Enable condition assignment{" "}
211 | activates the condition assignment feature. This allow you to request
212 | the next condition from DataPipe.
213 |
214 |
215 | We strongly recommend that you only activate the minimum features that
216 | you need for your experiment and that you only activate features during
217 | active data collection. This will reduce the risk of malicious use of
218 | DataPipe.
219 |
220 |
221 |
222 | Try it out!
223 |
224 |
225 | At this point you should be ready to collect data. We recommend testing
226 | data collection carefully at this point to ensure everything is properly
227 | configured. You should see data files created on your OSF data component
228 | immediately after you finish the experiment.
229 |
230 |
231 | );
232 | }
233 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { useContext } from "react";
3 | import { UserContext } from "../lib/context";
4 | import {
5 | Stack,
6 | VStack,
7 | Container,
8 | Heading,
9 | Text,
10 | Button,
11 | Image,
12 | } from "@chakra-ui/react";
13 |
14 | export default function Home() {
15 | const { user } = useContext(UserContext);
16 |
17 | return (
18 |
25 |
26 |
27 | Send data from your behavioral experiments to the Open Science
28 | Framework, for free.
29 |
30 | {user ? (
31 |
32 |
35 |
36 | ) : (
37 |
38 |
41 |
42 | )}
43 |
44 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/pages/redirect.js:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 | import { useEffect } from "react";
3 |
4 | export default function Redirect() {
5 | const router = useRouter();
6 |
7 | useEffect(() => {
8 | if (router.query.mode) {
9 | switch (router.query.mode) {
10 | case "resetPassword": {
11 | router.push("/reset-password?token=" + router.query?.oobCode);
12 | break;
13 | }
14 | default: {
15 | router.push("/");
16 | break;
17 | }
18 | }
19 | }
20 | }, [router, router.query]);
21 |
22 | return <>>;
23 | }
24 |
--------------------------------------------------------------------------------
/pages/reset-password.js:
--------------------------------------------------------------------------------
1 | import {
2 | Card,
3 | CardHeader,
4 | CardBody,
5 | Stack,
6 | Heading,
7 | Text,
8 | FormControl,
9 | FormLabel,
10 | FormErrorMessage,
11 | Input,
12 | Button,
13 | FormHelperText,
14 | } from "@chakra-ui/react";
15 |
16 | import { auth } from "../lib/firebase";
17 | import { sendPasswordResetEmail, confirmPasswordReset } from "firebase/auth";
18 |
19 | import { useEffect, useState } from "react";
20 | import { useRouter } from "next/router";
21 | import { getError } from "../lib/utils";
22 |
23 | export default function ResetPassword() {
24 | const router = useRouter();
25 | const [state, setState] = useState("forgot");
26 | const [email, setEmail] = useState("");
27 | const [password, setPassword] = useState("");
28 | const [error, setError] = useState("");
29 | const [token, setToken] = useState("");
30 | const [isSubmitting, setIsSubmitting] = useState(false);
31 |
32 | useEffect(() => {
33 | if (router.query?.token) {
34 | setState("token");
35 | setToken(router?.query?.token);
36 | }
37 | }, [router, router.query]);
38 |
39 | const resetPassword = async () => {
40 | setIsSubmitting(true);
41 | try {
42 | await sendPasswordResetEmail(auth, email);
43 | setIsSubmitting(false);
44 | setState("send");
45 | } catch (error) {
46 | setIsSubmitting(false);
47 | setError(getError(error.code));
48 | console.log("Password reset failed");
49 | console.log(error);
50 | }
51 | };
52 |
53 | const setNewPassword = async () => {
54 | if (password.length < 12) {
55 | setError("Password must be at least 12 characters");
56 | return;
57 | }
58 | setIsSubmitting(true);
59 | try {
60 | await confirmPasswordReset(auth, token, password);
61 | router.push("/admin");
62 | } catch (error) {
63 | setError(getError(error.code));
64 | setIsSubmitting(false);
65 | console.log(error);
66 | }
67 | };
68 |
69 | return (
70 |
71 |
72 | Reset your password
73 |
74 |
75 |
76 | {state === "send" && (
77 | We have sent you a link to reset your password.
78 | )}
79 | {state === "forgot" && (
80 | <>
81 |
82 | Email
83 | {
86 | setEmail(e.target.value);
87 | setError("");
88 | }}
89 | />
90 | {error}
91 |
92 |
93 | Enter your email and we will send you a link to reset your
94 | password.
95 |
96 |
103 | >
104 | )}
105 | {state === "token" && (
106 | <>
107 |
108 | New Password
109 | {
112 | setPassword(e.target.value);
113 | setError("");
114 | }}
115 | />
116 |
117 | Password must be at least 12 characters
118 |
119 | {error}
120 |
121 |
128 | >
129 | )}
130 |
131 |
132 |
133 | );
134 | }
135 |
--------------------------------------------------------------------------------
/pages/signin.js:
--------------------------------------------------------------------------------
1 | import SignInForm from "../components/SignInForm";
2 |
3 | export default function SignInPage() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/pages/signup.js:
--------------------------------------------------------------------------------
1 | import { createUserWithEmailAndPassword } from "firebase/auth";
2 | import { doc, setDoc } from "firebase/firestore";
3 | import Link from "next/link";
4 | import { useState } from "react";
5 | import { useRouter } from "next/router";
6 | import { auth, db } from "../lib/firebase";
7 |
8 | import {
9 | Card,
10 | CardBody,
11 | CardHeader,
12 | Heading,
13 | Link as ChakraLink,
14 | Text,
15 | FormControl,
16 | Stack,
17 | Input,
18 | FormLabel,
19 | Button,
20 | FormErrorMessage,
21 | Alert,
22 | AlertIcon,
23 | VStack,
24 | FormHelperText,
25 | } from "@chakra-ui/react";
26 | import { ERROR, getError } from "../lib/utils";
27 |
28 | export default function SignUpPage() {
29 | const router = useRouter();
30 | const [email, setEmail] = useState("");
31 | const [password, setPassword] = useState("");
32 | const [errorEmail, setErrorEmail] = useState("");
33 | const [errorPassword, setErrorPassword] = useState("");
34 | const [isSubmitting, setIsSubmitting] = useState(false);
35 |
36 | const onSubmit = async () => {
37 | if (password.length < 12) {
38 | setErrorPassword("Password must be at least 12 characters");
39 | return;
40 | }
41 |
42 | setIsSubmitting(true);
43 |
44 | try {
45 | const userCredential = await createUserWithEmailAndPassword(
46 | auth,
47 | email,
48 | password
49 | );
50 | const user = userCredential.user;
51 |
52 | await setDoc(doc(db, "users", user.uid), {
53 | email: user.email,
54 | uid: user.uid,
55 | osfToken: "",
56 | osfTokenValid: false,
57 | experiments: [],
58 | });
59 |
60 | router.push("/admin");
61 | } catch (error) {
62 | const { code } = error;
63 | if (ERROR.PASSWORD_WEAK === code) {
64 | setErrorPassword(getError(code));
65 | } else {
66 | setErrorEmail(getError(code));
67 | }
68 | setIsSubmitting(false);
69 | console.log(error);
70 | }
71 | };
72 |
73 | return (
74 |
75 |
76 |
77 | Sign Up
78 |
79 |
80 |
81 |
82 | Email
83 | {
86 | setEmail(e.target.value);
87 | setErrorEmail("");
88 | }}
89 | />
90 | {errorEmail}
91 |
92 |
93 | Password
94 | {
97 | setPassword(e.target.value);
98 | setErrorPassword("");
99 | }}
100 | />
101 |
102 | Password must be at least 12 characters
103 |
104 | {errorPassword}
105 |
106 |
107 |
114 |
115 | Have an account?{" "}
116 |
117 | Sign In!
118 |
119 |
120 |
121 |
122 |
123 |
124 | );
125 | }
126 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jspsych/datapipe/6240d63c423850e2c5bd2ae691e3a2f3457b171c/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jspsych/datapipe/6240d63c423850e2c5bd2ae691e3a2f3457b171c/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jspsych/datapipe/6240d63c423850e2c5bd2ae691e3a2f3457b171c/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jspsych/datapipe/6240d63c423850e2c5bd2ae691e3a2f3457b171c/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jspsych/datapipe/6240d63c423850e2c5bd2ae691e3a2f3457b171c/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jspsych/datapipe/6240d63c423850e2c5bd2ae691e3a2f3457b171c/public/favicon.ico
--------------------------------------------------------------------------------
/public/homepipe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jspsych/datapipe/6240d63c423850e2c5bd2ae691e3a2f3457b171c/public/homepipe.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jspsych/datapipe/6240d63c423850e2c5bd2ae691e3a2f3457b171c/public/logo.png
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 0 2rem;
3 | }
4 |
5 | .main {
6 | min-height: 100vh;
7 | padding: 4rem 0;
8 | flex: 1;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .footer {
16 | display: flex;
17 | flex: 1;
18 | padding: 2rem 0;
19 | border-top: 1px solid #eaeaea;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | .footer a {
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | flex-grow: 1;
29 | }
30 |
31 | .title a {
32 | color: #0070f3;
33 | text-decoration: none;
34 | }
35 |
36 | .title a:hover,
37 | .title a:focus,
38 | .title a:active {
39 | text-decoration: underline;
40 | }
41 |
42 | .title {
43 | margin: 0;
44 | line-height: 1.15;
45 | font-size: 4rem;
46 | }
47 |
48 | .title,
49 | .description {
50 | text-align: center;
51 | }
52 |
53 | .description {
54 | margin: 4rem 0;
55 | line-height: 1.5;
56 | font-size: 1.5rem;
57 | }
58 |
59 | .code {
60 | background: #fafafa;
61 | border-radius: 5px;
62 | padding: 0.75rem;
63 | font-size: 1.1rem;
64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
65 | Bitstream Vera Sans Mono, Courier New, monospace;
66 | }
67 |
68 | .grid {
69 | display: flex;
70 | align-items: center;
71 | justify-content: center;
72 | flex-wrap: wrap;
73 | max-width: 800px;
74 | }
75 |
76 | .card {
77 | margin: 1rem;
78 | padding: 1.5rem;
79 | text-align: left;
80 | color: inherit;
81 | text-decoration: none;
82 | border: 1px solid #eaeaea;
83 | border-radius: 10px;
84 | transition: color 0.15s ease, border-color 0.15s ease;
85 | max-width: 300px;
86 | }
87 |
88 | .card:hover,
89 | .card:focus,
90 | .card:active {
91 | color: #0070f3;
92 | border-color: #0070f3;
93 | }
94 |
95 | .card h2 {
96 | margin: 0 0 1rem 0;
97 | font-size: 1.5rem;
98 | }
99 |
100 | .card p {
101 | margin: 0;
102 | font-size: 1.25rem;
103 | line-height: 1.5;
104 | }
105 |
106 | .logo {
107 | height: 1em;
108 | margin-left: 0.5rem;
109 | }
110 |
111 | @media (max-width: 600px) {
112 | .grid {
113 | width: 100%;
114 | flex-direction: column;
115 | }
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | display: grid;
8 | height: 100%;
9 | }
10 |
11 | #__next {
12 | height: 100%;
13 | }
14 |
15 | a {
16 | color: inherit;
17 | text-decoration: none;
18 | }
19 |
20 | * {
21 | box-sizing: border-box;
22 | }
23 |
24 | .loader {
25 | border: 10px solid white;
26 | border-top: 10px solid darkblue;
27 | border-radius: 50%;
28 | width: 50px;
29 | height: 50px;
30 | animation: spin 2s linear infinite;
31 | }
32 |
33 | @keyframes spin {
34 | 0% {
35 | transform: rotate(0deg);
36 | }
37 | 100% {
38 | transform: rotate(360deg);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------