Skip to content

Commit a002f01

Browse files
authored
Merge pull request #66 from AndrewHUNGNguyen/#62-set-up-local-supabase-env
Add local setup to README
2 parents 6580192 + b940433 commit a002f01

21 files changed

Lines changed: 249 additions & 93 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ yarn-debug.log*
2626
yarn-error.log*
2727

2828
# local env files
29-
.env*
29+
.env**
3030

3131
# vercel
3232
.vercel

README.md

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,62 @@ NEXT_PUBLIC_SUPABASE_URL=<your-supabase-project-url>
4444
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-public-anon-key>
4545
```
4646

47-
These keys are safe to expose to the browser but should be scoped to the realtime channel only via Supabase RLS/policies.
47+
### Local Supabase Development
4848

49-
**Note: This project is being refactored to use styled-components exclusively. Please do not add new Tailwind classes. See [styling guidelines](./docs/conventions/styling-guidelines.md) for details.**
49+
The repo includes a Supabase CLI project under `supabase/` with config and migrations, so you can run the full stack locally.
50+
51+
#### 1. Prerequisites
52+
53+
- **Docker Desktop**[Install Docker Desktop](https://docs.docker.com/desktop/) and ensure it's running before starting Supabase.
54+
- **Supabase CLI**[Install the Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started#installing-the-supabase-cli) for your platform.
55+
56+
#### 2. Start the local Supabase stack
57+
58+
From the repo root:
59+
60+
```sh
61+
supabase start
62+
```
63+
64+
On success, you should see output including:
65+
66+
- **Project URL**: `http://127.0.0.1:54321`
67+
- **Studio**: `http://127.0.0.1:54323`
68+
- **Database**: `postgresql://postgres:postgres@127.0.0.1:54322/postgres`
69+
70+
If you change migrations or want a clean slate:
71+
72+
```sh
73+
supabase stop
74+
supabase db reset # WARNING: destroys local data, reapplies migrations
75+
supabase start
76+
```
77+
78+
#### 3. Point the Next.js app at local Supabase
79+
80+
Update `.env.local` in the project root:
81+
82+
```sh
83+
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
84+
NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH
85+
```
86+
87+
> Note: the `sb_publishable_...` key is printed in the `supabase start` output under “Authentication Keys → Publishable”.
88+
89+
Restart the dev server after changing `.env.local`.
90+
91+
#### 4. Authentication
92+
93+
For local testing, **username/password authentication** works out of the box – just sign up with any email and password in the app.
94+
95+
For OAuth providers (GitHub, Google), see the [Local OAuth Setup Guide](./docs/local-oauth-setup.md).
96+
97+
#### 5. Common local issues
98+
99+
- **`"no Route matched with those values"` at `127.0.0.1:54321`**
100+
This is normal for the bare API root. Use Studio (`http://127.0.0.1:54323`) or `http://localhost:3000` instead.
101+
- **Docker daemon errors (`Cannot connect to the Docker daemon`)**
102+
Make sure Docker Desktop is installed and running before you call `supabase start`.
50103

51104
## Contributing
52105

@@ -86,3 +139,7 @@ For detailed code style and organization guidelines:
86139
git push origin feature-name
87140
```
88141
5. **Open a pull request on Github**
142+
143+
## Footnotes
144+
145+
> **Note:** This project is being refactored to use styled-components exclusively. Please do not add new Tailwind classes. See [styling guidelines](./docs/conventions/styling-guidelines.md) for details.

app/login/page.tsx

Lines changed: 115 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default function Login() {
1717
const [signingIn, setSigningIn] = useState(false)
1818
const [signingUp, setSigningUp] = useState(false)
1919
const [signupSuccess, setSignupSuccess] = useState(false)
20+
const [showEmailForm, setShowEmailForm] = useState(false)
2021
const router = useRouter()
2122

2223
useEffect(() => {
@@ -81,7 +82,7 @@ export default function Login() {
8182
checkAuth()
8283
}, [router])
8384

84-
const handleLogin = async (provider: "google" | "github") => {
85+
const handleLogin = (provider: "google" | "github") => async () => {
8586
try {
8687
setError(null)
8788
// Get redirect URL from query params to preserve it through OAuth flow
@@ -188,16 +189,35 @@ export default function Login() {
188189

189190
if (signUpError) throw signUpError
190191

191-
// Email confirmation required - show success message
192-
setError(null)
193-
setSignupSuccess(true)
192+
// Check if email confirmation is required by checking if session exists
193+
// Supabase only returns a session on signup if email confirmation is disabled
194+
if (data.session) {
195+
// No email confirmation required - redirect to setup
196+
const setupUrl = redirectUrl
197+
? `/setup?redirect=${encodeURIComponent(redirectUrl)}`
198+
: "/setup"
199+
router.push(setupUrl)
200+
} else {
201+
// Email confirmation required - show success message
202+
setSignupSuccess(true)
203+
}
194204
} catch (err: any) {
195205
setError(err.message)
196206
} finally {
197207
setSigningUp(false)
198208
}
199209
}
200210

211+
const handleBackClick = () => {
212+
setError(null)
213+
setShowEmailForm(false)
214+
}
215+
216+
const handleContinueEmailClick = () => {
217+
setError(null)
218+
setShowEmailForm(true)
219+
}
220+
201221
return (
202222
<>
203223
<BackgroundContainer>
@@ -219,80 +239,83 @@ export default function Login() {
219239

220240
{error && <ErrorMessage>{error}</ErrorMessage>}
221241

222-
<ButtonContainer>
223-
<Button onClick={() => handleLogin("google")} size="default" variant="primary">
224-
Continue with Google
225-
</Button>
226-
227-
<Button onClick={() => handleLogin("github")} size="default" variant="secondary">
228-
Continue with GitHub
229-
</Button>
230-
</ButtonContainer>
231-
232-
<Divider />
233-
234-
<EmailForm
235-
onSubmit={(e) => {
236-
e.preventDefault()
237-
handleEmailSignIn(e)
238-
}}
239-
>
240-
<FormTitle>Sign In with Email</FormTitle>
241-
<InputGroup>
242-
<TextInput
243-
type="email"
244-
variant="secondary"
245-
size="default"
246-
value={email}
247-
onChange={(e) => setEmail(e.target.value)}
248-
placeholder="Email"
249-
required
250-
disabled={signingIn || signingUp}
251-
/>
252-
</InputGroup>
253-
<InputGroup>
254-
<TextInput
255-
type="password"
256-
variant="secondary"
257-
size="default"
258-
value={password}
259-
onChange={(e) => setPassword(e.target.value)}
260-
placeholder="Password"
261-
required
262-
minLength={6}
263-
disabled={signingIn || signingUp}
264-
/>
265-
<PasswordHelpText>
266-
Password must be at least 6 characters with lowercase, uppercase, digits, and
267-
symbols
268-
</PasswordHelpText>
269-
</InputGroup>
270-
<ButtonWrapper>
271-
<Button
272-
type="submit"
273-
size="default"
274-
variant="primary"
275-
disabled={signingIn || signingUp || !email || !password}
276-
>
277-
{signingIn ? "Signing in..." : "Sign In"}
242+
{showEmailForm ? (
243+
<EmailForm
244+
onSubmit={(e) => {
245+
e.preventDefault()
246+
handleEmailSignIn(e)
247+
}}
248+
>
249+
<InputGroup>
250+
<TextInput
251+
type="email"
252+
variant="secondary"
253+
size="default"
254+
value={email}
255+
onChange={(e) => setEmail(e.target.value)}
256+
placeholder="Email"
257+
required
258+
disabled={signingIn || signingUp}
259+
/>
260+
</InputGroup>
261+
<InputGroup>
262+
<TextInput
263+
type="password"
264+
variant="secondary"
265+
size="default"
266+
value={password}
267+
onChange={(e) => setPassword(e.target.value)}
268+
placeholder="Password"
269+
required
270+
minLength={6}
271+
disabled={signingIn || signingUp}
272+
/>
273+
<PasswordHelpText>Password must be at least 6 characters</PasswordHelpText>
274+
</InputGroup>
275+
<ButtonWrapper>
276+
<Button
277+
type="submit"
278+
size="default"
279+
variant="primary"
280+
disabled={signingIn || signingUp || !email || !password}
281+
>
282+
{signingIn ? "Signing in..." : "Sign In"}
283+
</Button>
284+
<Button
285+
type="button"
286+
size="default"
287+
variant="secondary"
288+
disabled={signingIn || signingUp || !email || !password}
289+
onClick={(e) => {
290+
if (e) {
291+
e.preventDefault()
292+
e.stopPropagation()
293+
}
294+
handleEmailSignUp(e!)
295+
}}
296+
>
297+
{signingUp ? "Signing up..." : "Sign Up"}
298+
</Button>
299+
</ButtonWrapper>
300+
<BackButton type="button" onClick={handleBackClick}>
301+
← Back to sign in options
302+
</BackButton>
303+
</EmailForm>
304+
) : (
305+
<ButtonContainer>
306+
<Button onClick={handleLogin("google")} size="default" variant="primary">
307+
Continue with Google
308+
</Button>
309+
310+
<Button onClick={handleLogin("github")} size="default" variant="secondary">
311+
Continue with GitHub
278312
</Button>
279-
<Button
280-
type="button"
281-
size="default"
282-
variant="secondary"
283-
disabled={signingIn || signingUp || !email || !password}
284-
onClick={(e) => {
285-
if (e) {
286-
e.preventDefault()
287-
e.stopPropagation()
288-
}
289-
handleEmailSignUp(e!)
290-
}}
291-
>
292-
{signingUp ? "Signing up..." : "Sign Up"}
313+
314+
<Button onClick={handleContinueEmailClick} size="default" variant="tertiary">
315+
Continue with Email
293316
</Button>
294-
</ButtonWrapper>
295-
</EmailForm>
317+
</ButtonContainer>
318+
)}
296319
</>
297320
)}
298321
</PageContainer>
@@ -354,12 +377,6 @@ const ErrorMessage = styled.div`
354377
width: 100%;
355378
`
356379

357-
const Divider = styled.hr`
358-
border: none;
359-
border-top: 1px solid rgba(255, 255, 255, 0.2);
360-
width: 100%;
361-
`
362-
363380
const EmailForm = styled.form`
364381
display: flex;
365382
flex-direction: column;
@@ -381,14 +398,6 @@ const ButtonWrapper = styled.div`
381398
}
382399
`
383400

384-
const FormTitle = styled.h2`
385-
font-size: 1.25rem;
386-
font-weight: 600;
387-
color: rgba(255, 255, 255, 0.9);
388-
margin: 0;
389-
text-align: center;
390-
`
391-
392401
const InputGroup = styled.div`
393402
display: flex;
394403
flex-direction: column;
@@ -406,3 +415,19 @@ const PasswordHelpText = styled.p`
406415
const SuccessContainer = styled.div`
407416
width: 100%;
408417
`
418+
419+
const BackButton = styled.button`
420+
background: none;
421+
border: none;
422+
color: rgba(255, 255, 255, 0.6);
423+
font-size: 0.875rem;
424+
cursor: pointer;
425+
padding: 0.5rem;
426+
margin-top: 0.5rem;
427+
transition: color 0.2s ease;
428+
font-family: inherit;
429+
430+
&:hover {
431+
color: rgba(255, 255, 255, 0.9);
432+
}
433+
`

0 commit comments

Comments
 (0)